7.1 Uso incorrecto de expresiones como valores predeterminados para argumentos de funciones
Ya has aprendido mucho, así que analicemos los errores más comunes de principiantes: es mejor aprender de los errores ajenos que de los propios, ¿no es así?
Python permite indicar que una función puede tener argumentos opcionales, asignando un valor predeterminado a ellos. Esta es, por supuesto, una característica muy conveniente del lenguaje, pero puede llevar a consecuencias desagradables si el tipo de dicho valor es mutable. Por ejemplo, consideremos la siguiente definición de función:
def foo(bar=[]): # bar es un argumento opcional
# y por defecto es una lista vacía.
bar.append("baz") # esta línea podría ser un problema...
return bar
Un error común en este caso es pensar que el valor del argumento opcional se establecerá en el valor predeterminado cada vez que la función se llame sin un valor para ese argumento.
En el código anterior, por ejemplo, uno podría suponer que al llamar repetidamente a la función foo() (es decir, sin especificar un valor para el argumento bar), siempre devolverá ["baz"], ya que se supone que cada vez que foo() se llama (sin especificar el argumento bar), bar se establece en [] (es decir, una nueva lista vacía).
Pero veamos qué sucede en realidad:
result = foo()
print(result) # ["baz"]
result = foo()
print(result) # ["baz", "baz"]
result = foo()
print(result) # ["baz", "baz", "baz"]
¿Por qué la función sigue añadiendo el valor baz a la lista existente cada vez que se llama a foo(), en lugar de crear una nueva lista cada vez?
La respuesta a esta pregunta es un entendimiento más profundo de lo que sucede en Python "bajo el capó". A saber: el valor predeterminado para la función se inicializa solo una vez, durante la definición de la función.
Por lo tanto, el argumento bar se inicializa por defecto (es decir, con una lista vacía) solo cuando foo() se define por primera vez, pero las llamadas subsiguientes a foo() (es decir, sin especificar el argumento bar) continuarán utilizando la misma lista que se creó para el argumento bar en el momento en que se definió por primera vez la función.
Como referencia, una "solución" común para este error es la siguiente definición:
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 incorrecto de variables de clase
Consideremos el siguiente ejemplo:
class A(object):
x = 1
class B(A):
pass
class C(A):
pass
print(A.x, B.x, C.x)
# 1 1 1
Parece todo bien.
Ahora, asignemos un valor al campo x de la clase B:
B.x = 2
print(A.x, B.x, C.x)
# 1 2 1
Todo como se esperaba.
Y ahora cambiemos la variable de la clase A:
A.x = 3
print(A.x, B.x, C.x)
# 3 2 3
Es raro, solo cambiamos A.x. ¿Por qué C.x también cambió?
En Python, las variables de clase se manejan como diccionarios y siguen lo que a menudo se llama el Orden de Resolución de Métodos (MRO). Por lo tanto, en el código anterior, como el atributo x no se encuentra en la clase C, se buscará en sus clases base (solo A en el ejemplo anterior, aunque Python admite la herencia múltiple).
En otras palabras, C no tiene su propia propiedad x, independiente de A. Por lo tanto, las referencias a C.x son, de hecho, referencias a A.x. Esto puede causar problemas si no se consideran tales casos adecuadamente. Al estudiar Python, presta especial atención a los atributos de clase y su manejo.
7.3 Especificación incorrecta de parámetros para el bloque de excepciones
Supongamos que tienes el siguiente fragmento de código:
try:
l = ["a", "b"]
int(l[2])
except ValueError, IndexError: # ¿Para capturar ambas excepciones, verdad?
pass
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
IndexError: list index out of range
El problema aquí es que la expresión except no acepta una lista de excepciones especificadas de esta manera. Como resultado, en el código anterior, la excepción IndexError no es capturada por la expresión except; en su lugar, la excepción termina vinculándose al parámetro llamado IndexError.
¡Importante! Puedes encontrar este tipo de código en ejemplos en Internet, ya que se usaba en Python 2.x.
La forma correcta de capturar múltiples excepciones usando la expresión except es especificar el primer parámetro como una tupla que contiene todas las excepciones que se desean capturar. Además, para una mejor legibilidad, puedes usar la palabra clave as, por ejemplo:
try:
l = ["a", "b"]
int(l[2])
except (ValueError, IndexError) as e:
pass
GO TO FULL VERSION