CodeGym /Cursos /Python SELF ES /Errores estándar

Errores estándar

Python SELF ES
Nivel 20 , Lección 1
Disponible

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
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION