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