7.1 Uso incorreto de expressões como valores padrão para argumentos de funções
Você já aprendeu muito, então vamos dar uma olhada nos erros mais comuns de iniciantes – é melhor aprender com os erros dos outros do que com os seus próprios, né?
Python permite especificar que funções podem ter argumentos opcionais atribuindo-lhes um valor padrão. Isso é uma característica muito útil da linguagem, mas pode levar a consequências indesejadas se o tipo desse valor for mutável. Por exemplo, considere a seguinte definição de função:
def foo(bar=[]): # bar - é um argumento opcional
# e por padrão é igual a uma lista vazia.
bar.append("baz") # essa linha pode causar problemas...
return bar
Um erro comum aqui é pensar que o valor do argumento opcional será definido para o valor padrão cada vez que a função for chamada sem um valor para esse argumento.
No código acima, por exemplo, pode-se supor que chamando novamente a função foo() (ou seja, sem especificar um valor para o argumento bar), ela sempre retornará ["baz"], já que se supõe que cada vez que foo() é chamado (sem especificar o argumento bar), bar é definido para [] (ou seja, uma nova lista vazia).
Mas vamos ver o que acontece na realidade:
result = foo()
print(result) # ["baz"]
result = foo()
print(result) # ["baz", "baz"]
result = foo()
print(result) # ["baz", "baz", "baz"]
Por que a função continua adicionando o valor baz à lista existente cada vez que foo() é chamada, em vez de criar uma nova lista a cada vez?
A resposta para essa pergunta é um entendimento mais profundo do que acontece em Python "por baixo do capô". Ou seja: o valor padrão para uma função é inicializado apenas uma vez, no momento da definição da função.
Assim, o argumento bar é inicializado com o valor padrão (ou seja, uma lista vazia) somente quando foo() é definido pela primeira vez, mas chamadas subsequentes para foo() (ou seja, sem especificar o argumento bar) continuarão a usar a mesma lista que foi criada para o argumento bar no momento da primeira definição da função.
Para referência, uma "solução alternativa" comum para este erro é a seguinte definição:
def foo(bar=None):
if bar is None:
bar = []
bar.append("baz")
return bar
result = foo()
print(result) # ["baz"]
result = foo()
print(result) # ["baz"]
result = foo()
print(result) # ["baz"]
7.2 Uso incorreto de variáveis de classe
Considere o seguinte exemplo:
class A(object):
x = 1
class B(A):
pass
class C(A):
pass
print(A.x, B.x, C.x)
# 1 1 1
Parece tudo certo.
Agora vamos atribuir um valor ao campo x da classe B:
B.x = 2
print(A.x, B.x, C.x)
# 1 2 1
Tudo como esperado.
Agora vamos mudar a variável de classe A:
A.x = 3
print(A.x, B.x, C.x)
# 3 2 3
Estranho, apenas mudamos A.x. Por que C.x também mudou?
Em Python, variáveis de classe são tratadas como dicionários e seguem o que muitas vezes é chamado de Ordem de Resolução de Métodos (MRO). Assim, no código acima, como o atributo x não é encontrado na classe C, ele será encontrado em suas classes base (apenas A no exemplo acima, embora Python suporte herança múltipla).
Em outras palavras, C não tem sua própria propriedade x, independente de A. Assim, referências a C.x são na verdade referências a A.x. Isso pode causar problemas se tais casos não forem devidamente levados em consideração. Ao estudar Python, preste atenção especial aos atributos de classe e como lidar com eles.
7.3 Especificação incorreta de parâmetros para o bloco de exceção
Suponha que você tenha o seguinte pedaço de código:
try:
l = ["a", "b"]
int(l[2])
except ValueError, IndexError: # Para capturar ambas exceções, certo?
pass
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
IndexError: list index out of range
O problema aqui é que a expressão except não aceita uma lista de exceções especificadas dessa maneira. Como resultado, no código acima a exceção IndexError não é capturada pela expressão except; em vez disso, a exceção acaba sendo vinculada a um parâmetro chamado IndexError.
Importante! Você pode encontrar tal código em exemplos na internet, pois ele era usado no Python 2.x.
A maneira correta de capturar várias exceções com uma expressão except é especificar o primeiro parâmetro como uma tupla contendo todas as exceções que você deseja capturar. Além disso, para legibilidade, você pode usar a palavra-chave as, como este exemplo:
try:
l = ["a", "b"]
int(l[2])
except (ValueError, IndexError) as e:
pass
GO TO FULL VERSION