C贸digo de error de Python: 10 errores m谩s comunes que cometen los desarrolladores

Sobre Python


Python es un lenguaje de programaci贸n interpretado, orientado a objetos y de alto nivel con sem谩ntica din谩mica. Las estructuras de datos integradas de alto nivel combinadas con la tipificaci贸n din谩mica y el enlace din谩mico lo hacen muy atractivo para BRPS (desarrollo r谩pido de herramientas de aplicaci贸n), as铆 como para usarlo como un lenguaje de scripting y conexi贸n para conectar componentes o servicios existentes. Python admite m贸dulos y paquetes, fomentando as铆 la modularidad del programa y la reutilizaci贸n de c贸digo.

Sobre este articulo


La simplicidad y la facilidad de aprender este lenguaje pueden ser confusas para los desarrolladores (especialmente aquellos que reci茅n comienzan a aprender Python), por lo que puede perder de vista algunas sutilezas importantes y subestimar el poder de la variedad de soluciones posibles que usan Python.

Con esto en mente, este art铆culo presenta los "10 principales" errores sutiles y dif铆ciles de encontrar que incluso los desarrolladores avanzados de Python pueden cometer.

Error # 1: mal uso de expresiones como valores predeterminados para argumentos de funci贸n


Python le permite indicar que una funci贸n puede tener argumentos opcionales estableciendo un valor predeterminado para ellos. Esto, por supuesto, es una caracter铆stica muy conveniente del lenguaje, pero puede tener consecuencias desagradables si el tipo de este valor es mutable. Por ejemplo, considere la siguiente definici贸n de funci贸n:

>>> def foo(bar=[]): # bar -    #      . ... bar.append("baz") #     ... ... return bar 

Un error com煤n en este caso es pensar que el valor de un argumento opcional se establecer谩 en el valor predeterminado cada vez que se llame a una funci贸n sin un valor para este argumento. En el c贸digo anterior, por ejemplo, podemos suponer que al llamar repetidamente a la funci贸n foo () (es decir, sin especificar un valor para el argumento de la barra), siempre devolver谩 "baz", ya que se supone que cada vez que se llama a foo () especificando la barra de argumentos), la barra se establece en [] (es decir, una nueva lista vac铆a).

Pero veamos qu茅 suceder谩 realmente:

 >>> foo() ["baz"] >>> foo() ["baz", "baz"] >>> foo() ["baz", "baz", "baz"] 

驴Eh? 驴Por qu茅 la funci贸n contin煤a agregando el valor predeterminado "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 ser谩 una comprensi贸n m谩s profunda de lo que est谩 sucediendo con 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 de la barra se inicializa de manera predeterminada (es decir, una lista vac铆a) solo cuando se define foo () por primera vez, pero las llamadas posteriores a foo () (es decir, sin especificar el argumento de la barra) continuar谩n utilizando la misma lista que estaba creado para la barra de argumentos en el momento de la primera definici贸n de 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: # or if not bar: ... bar = [] ... bar.append("baz") ... return bar ... >>> foo() ["baz"] >>> foo() ["baz"] >>> foo() ["baz"] 

Error # 2: mal uso de las variables de clase


Considere el siguiente ejemplo:

 >>> class A(object): ... x = 1 ... >>> class B(A): ... pass ... >>> class C(A): ... pass ... >>> print Ax, Bx, Cx 1 1 1 

Todo parece estar en orden.

 >>> Bx = 2 >>> print Ax, Bx, Cx 1 2 1 

S铆, todo fue como se esperaba.

 >>> Ax = 3 >>> print Ax, Bx, Cx 3 2 3 

驴Qu茅 demonios? Acabamos de cambiar Ax. 驴Por qu茅 tambi茅n cambi贸 Cx?

En Python, las variables de clase se tratan como diccionarios y siguen lo que a menudo se llama Orden de resoluci贸n de m茅todo (MRO). Por lo tanto, en el c贸digo anterior, dado que el atributo x no se encuentra en la clase C, se encontrar谩 en sus clases base (solo A en el ejemplo anterior, aunque Python admite herencia m煤ltiple). En otras palabras, C no tiene su propia propiedad x independiente de A. Por lo tanto, las referencias a Cx son en realidad referencias a Ax. Esto causar谩 problemas si estos casos no se manejan adecuadamente. Entonces, cuando aprenda Python, preste especial atenci贸n a los atributos de la clase y trabaje con ellos.

Error No. 3: par谩metros incorrectos para el bloque de excepci贸n


Supongamos que tiene el siguiente c贸digo:

 >>> try: ... l = ["a", "b"] ... int(l[2]) ... except ValueError, IndexError: # To catch both exceptions, right? ... 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 de excepci贸n no acepta la lista de excepciones especificada de esta manera. M谩s bien, en Python 2.x, la expresi贸n "excepto Excepci贸n, e" se usa para vincular la excepci贸n a un segundo par谩metro opcional dado en segundo lugar (en este caso, e) para que est茅 disponible para una inspecci贸n adicional. Como resultado, en el c贸digo anterior, la instrucci贸n except no captura una excepci贸n IndexError; m谩s bien, la excepci贸n termina con el enlace a un par谩metro llamado IndexError en su lugar.

La forma correcta de capturar varias excepciones con la expresi贸n de excepci贸n es especificar el primer par谩metro como una tupla que contiene todas las excepciones que desea capturar. Adem谩s, para obtener la m谩xima compatibilidad, use la palabra clave as, ya que esta sintaxis es compatible con Python 2 y Python 3:

 >>> try: ... l = ["a", "b"] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>> 

Error # 4: entender mal las reglas de alcance de Python


El alcance en Python se basa en la llamada regla LEGB, que es una abreviatura de Local (nombres asignados de cualquier manera dentro de una funci贸n (def o lambda), y no declarada global en esta funci贸n), Encerrando (nombre en el alcance local de cualquier funci贸n que incluya est谩ticamente ( def o lambda), de interno a externo), Global (nombres asignados en el nivel superior del archivo del m贸dulo, o ejecutando las instrucciones globales en def dentro del archivo), Integrado (nombres previamente asignados en el m贸dulo de nombre incorporado: abierto, rango, SyntaxError, ...). Parece bastante simple, 驴verdad? Bueno, en realidad, hay algunas sutilezas sobre c贸mo funciona esto en Python, lo que nos lleva al problema general de programaci贸n de Python m谩s complejo a continuaci贸n. Considere el siguiente ejemplo:

 >>> x = 10 >>> def foo(): ... x += 1 ... print x ... >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment 

Cual es el problema

El error anterior se produce porque cuando asigna una variable en el alcance, Python autom谩ticamente lo considera local a ese alcance y oculta cualquier variable con el mismo nombre en cualquier alcance principal.

Por lo tanto, muchos se sorprenden cuando reciben UnboundLocalError en el c贸digo que se estaba ejecutando anteriormente, cuando se modifica agregando un operador de asignaci贸n en alg煤n lugar del cuerpo de la funci贸n.

Esta caracter铆stica es especialmente confusa para los desarrolladores cuando usan listas. Considere el siguiente ejemplo:

 >>> lst = [1, 2, 3] >>> def foo1(): ... lst.append(5) #   ... ... >>> foo1() >>> lst [1, 2, 3, 5] >>> lst = [1, 2, 3] >>> def foo2(): ... lst += [5] # ...    ! ... >>> foo2() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'lst' referenced before assignment 

驴Eh? 驴Por qu茅 se bloquea foo2 mientras foo1 funciona bien?

La respuesta es la misma que en el ejemplo anterior, pero, seg煤n la creencia popular, la situaci贸n aqu铆 es m谩s sutil. foo1 no aplica el operador de asignaci贸n a lst, mientras que foo2 no. Teniendo en cuenta que lst + = [5] en realidad es solo una abreviatura de lst = lst + [5], vemos que estamos tratando de asignar el valor lst (por lo que Python supone que est谩 en el 谩mbito local). Sin embargo, el valor que queremos asignar a lst se basa en lst en s铆 mismo (de nuevo, ahora se supone que est谩 en el 谩mbito local), que a煤n no se ha determinado. Y tenemos un error.

Error # 5: cambiar una lista durante la iteraci贸n sobre ella


El problema en el siguiente fragmento de c贸digo deber铆a ser bastante obvio:

 >>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> for i in range(len(numbers)): ... if odd(numbers[i]): ... del numbers[i] # BAD: Deleting item from a list while iterating over it ... Traceback (most recent call last): File "<stdin>", line 2, in <module> IndexError: list index out of range 

Eliminar un elemento de una lista o matriz durante la iteraci贸n es un problema de Python que es bien conocido por cualquier desarrollador de software experimentado. Pero, aunque el ejemplo anterior puede ser bastante obvio, incluso los desarrolladores experimentados pueden embarcarse en este rastrillo en un c贸digo mucho m谩s complejo.

Afortunadamente, Python incluye una serie de paradigmas de programaci贸n elegantes que, cuando se usan correctamente, pueden conducir a una simplificaci贸n y optimizaci贸n de c贸digo significativas. Una consecuencia agradable adicional de esto es que en un c贸digo m谩s simple, la probabilidad de caer en el error de eliminar accidentalmente un elemento de la lista durante la iteraci贸n es mucho menor. Uno de esos paradigmas son los generadores de listas. Adem谩s, comprender el funcionamiento de los generadores de listas es especialmente 煤til para evitar este problema en particular, como se muestra en esta implementaci贸n alternativa del c贸digo anterior, que funciona bien:

 >>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all >>> numbers [0, 2, 4, 6, 8] 

Error # 6: malentendido c贸mo Python une las variables en los cierres


Considere el siguiente ejemplo:

 >>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ... 

Puede esperar el siguiente resultado:

 0 2 4 6 8 

Pero en realidad entiendes esto:

 8 8 8 8 8 

Sorpresa!

Esto se debe al enlace tard铆o en Python, lo que significa que los valores de las variables utilizadas en los cierres se buscan durante la llamada a la funci贸n interna. Por lo tanto, en el c贸digo anterior, cada vez que se llama a cualquiera de las funciones devueltas, se busca el valor i en el alcance circundante durante su llamada (y para ese momento el ciclo ya se hab铆a completado, por lo que ya se me hab铆a asignado el resultado final - valor 4) .

La soluci贸n a este problema com煤n de Python ser铆a:

 >>> def create_multipliers(): ... return [lambda x, i=i : i * x for i in range(5)] ... >>> for multiplier in create_multipliers(): ... print multiplier(2) ... 0 2 4 6 8 

Voila! Usamos los argumentos predeterminados aqu铆 para generar funciones an贸nimas para lograr el comportamiento deseado. Algunos llamar铆an a esta soluci贸n elegante. Algunos son
delgado Algunas personas odian estas cosas. Pero si eres un desarrollador de Python, de todos modos, es importante entenderlo.

Error # 7: crear dependencias de m贸dulos c铆clicos


Supongamos que tiene dos archivos, a.py y b.py, cada uno de los cuales importa el otro, de la siguiente manera:

En a.py:

 import b def f(): return bx print f() 

En b.py:

 import a x = 1 def g(): print af() 

Primero, intente importar a.py:

 >>> import a 1 

Funcion贸 bien. Esto puede sorprenderte. Despu茅s de todo, los m贸dulos se importan entre s铆 c铆clicamente y esto probablemente deber铆a ser un problema, 驴verdad?

La respuesta es que simplemente tener una importaci贸n c铆clica de m贸dulos no es en s铆 un problema en Python. Si el m贸dulo ya se ha importado, Python es lo suficientemente inteligente como para no intentar volver a importarlo. Sin embargo, dependiendo del punto en el que cada m贸dulo intente acceder a las funciones o variables definidas en otro, es posible que tenga problemas.

Entonces, volviendo a nuestro ejemplo, cuando importamos a.py, no tuvo problemas para importar b.py, ya que b.py no requiere que se defina nada de a.py durante su importaci贸n. La 煤nica referencia en b.py a a es una llamada a af (). Pero esta llamada en g () y nada en a.py o b.py no llama a g (). Entonces todo funciona bien.

Pero, 驴qu茅 sucede si intentamos importar b.py (sin importar primero a.py, es decir):

 >>> import b Traceback (most recent call last): File "<stdin>", line 1, in <module> File "b.py", line 1, in <module> import a File "a.py", line 6, in <module> print f() File "a.py", line 4, in f return bx AttributeError: 'module' object has no attribute 'x' 

Oh oh 隆Esto no es bueno! El problema aqu铆 es que durante el proceso de importaci贸n de b.py intenta importar a.py, que a su vez llama a f (), que intenta acceder a bx, pero bx a煤n no se ha definido. De ah铆 la excepci贸n AttributeError.

Al menos una soluci贸n a este problema es bastante trivial. Simplemente modifique b.py para importar a.py en g ():

 x = 1 def g(): import a # This will be evaluated only when g() is called print af() 

Ahora cuando lo importamos, todo est谩 bien:

 >>> import b >>> bg() 1 # Printed a first time since module 'a' calls 'print f()' at the end 1 # Printed a second time, this one is our call to 'g' 

Error # 8: intersecci贸n de nombres con nombres de m贸dulos en la biblioteca est谩ndar de Python


Uno de los encantos de Python son sus muchos m贸dulos que salen de la caja. Pero como resultado, si no sigue esto conscientemente, puede encontrar que el nombre de su m贸dulo puede tener el mismo nombre que el m贸dulo en la biblioteca est谩ndar que viene con Python (por ejemplo, en su c贸digo puede haber un m贸dulo con el nombre email.py, que entrar谩 en conflicto con el m贸dulo de biblioteca est谩ndar con el mismo nombre).

Esto puede conducir a problemas serios. Por ejemplo, si alguno de los m贸dulos intenta importar la versi贸n del m贸dulo de la biblioteca est谩ndar de Python, y tiene un m贸dulo en el proyecto con el mismo nombre, que se importar谩 por error en lugar del m贸dulo de la biblioteca est谩ndar.

Por lo tanto, se debe tener cuidado de no usar los mismos nombres que en los m贸dulos de la biblioteca est谩ndar de Python. Es mucho m谩s f谩cil cambiar el nombre del m贸dulo en su proyecto que enviar una solicitud para cambiar el nombre del m贸dulo en la biblioteca est谩ndar y obtener su aprobaci贸n.

Error # 9: no tener en cuenta las diferencias entre Python 2 y Python 3


Considere el siguiente archivo foo.py:

 import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def bad(): e = None try: bar(int(sys.argv[1])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e) bad() 

En Python 2, funcionar谩 bien:

 $ python foo.py 1 key error 1 $ python foo.py 2 value error 2 

Pero ahora veamos c贸mo funcionar谩 en Python 3:

 $ python3 foo.py 1 key error Traceback (most recent call last): File "foo.py", line 19, in <module> bad() File "foo.py", line 17, in bad print(e) UnboundLocalError: local variable 'e' referenced before assignment 

驴Qu茅 acaba de pasar aqu铆? El "problema" es que en Python 3, un objeto en un bloque de excepci贸n no est谩 disponible fuera de 茅l. (La raz贸n de esto es que, de lo contrario, los objetos en este bloque se almacenar谩n en la memoria hasta que el recolector de basura se inicie y elimine las referencias a ellos desde all铆).

Una forma de evitar este problema es mantener la referencia al objeto del bloque de excepci贸n fuera de este bloque para que permanezca disponible. Aqu铆 est谩 la versi贸n del ejemplo anterior que utiliza esta t茅cnica, obteniendo as铆 el c贸digo adecuado tanto para Python 2 como para Python 3:

 import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def good(): exception = None try: bar(int(sys.argv[1])) except KeyError as e: exception = e print('key error') except ValueError as e: exception = e print('value error') print(exception) good() 

Ejec煤talo en Python 3:

 $ python3 foo.py 1 key error 1 $ python3 foo.py 2 value error 2 

隆Hurra!

Error # 10: uso incorrecto del m茅todo __del__


Digamos que tiene un archivo mod.py como este:

 import foo class Bar(object): ... def __del__(self): foo.cleanup(self.myhandle) 

Y est谩 intentando hacer esto desde otro another_mod.py:

 import mod mybar = mod.Bar() 

Y obt茅n un terrible AttributeError.

Por qu茅 Porque, como se informa aqu铆 , cuando el int茅rprete se apaga, todas las variables globales del m贸dulo tienen el valor Ninguno. Como resultado, en el ejemplo anterior, cuando se llam贸 a __del__, el nombre foo ya estaba establecido en Ninguno.

La soluci贸n a esta "tarea con un asterisco" es usar atexit.register (). Por lo tanto, cuando su programa completa la ejecuci贸n (es decir, cuando sale normalmente), sus identificadores se eliminan antes de que el int茅rprete complete su trabajo.

Con esto en mente, la soluci贸n para el c贸digo mod.py anterior podr铆a verse as铆:

 import foo import atexit def cleanup(handle): foo.cleanup(handle) class Bar(object): def __init__(self): ... atexit.register(cleanup, self.myhandle) 

Dicha implementaci贸n proporciona una manera simple y confiable de llamar a cualquier limpieza necesaria despu茅s de la finalizaci贸n normal del programa. Obviamente, la decisi贸n sobre c贸mo tratar con el objeto que est谩 asociado con el nombre self.myhandle se deja a foo.cleanup, pero creo que entiendes la idea.

Conclusi贸n


Python es un lenguaje potente y flexible con muchos mecanismos y paradigmas que pueden mejorar significativamente el rendimiento. Sin embargo, como con cualquier herramienta de software o lenguaje, con una comprensi贸n o evaluaci贸n limitada de sus capacidades, pueden surgir problemas imprevistos durante el desarrollo.

Una introducci贸n a los matices de Python cubiertos en este art铆culo ayudar谩 a optimizar su uso del lenguaje, evitando algunos errores comunes.

Source: https://habr.com/ru/post/466441/


All Articles