9.1 Creación de dependencias cíclicas de módulos
Supongamos que tienes dos archivos, a.py y b.py, cada uno de los cuales importa al otro de la siguiente manera:
En a.py:
import b
def f():
return b.x
print(f())
En b.py:
import a
x = 1
def g():
print(a.f())
Primero intentemos importar a.py:
import a
# 1
Funcionó de maravilla. Quizás esto te sorprenda. Después de todo, los módulos se importan cíclicamente y eso probablemente debería ser un problema, ¿verdad?
La respuesta es que el simple hecho de tener una importación cíclica de módulos no es en sí mismo un problema en Python. Si un módulo ya ha sido importado, Python es lo bastante inteligente como para no intentar importarlo de nuevo. Sin embargo, dependiendo de en qué momento cada módulo intente acceder a funciones o variables, definidas en el otro, realmente puedes encontrarte con problemas.
Entonces, volviendo a nuestro ejemplo, cuando importamos a.py, no tuvo problemas en importar b.py, ya que b.py no requiere que nada de a.py esté definido en el momento de su importación. La única referencia en b.py a a es la llamada a a.f(). Pero esa llamada está en g(), y nada en a.py o b.py llama a g(). Así que todo funciona perfectamente.
Pero, ¿qué pasa si intentamos importar b.py (sin haber importado previamente a.py, es decir):
import b
Traceback (most recent call last):
File "<stdin>", line 1, in
File "b.py", line 1, in
import a File "a.py", line 6, in
print(f()) File "a.py", line 4, in f return b.x AttributeError: 'module' object has no attribute 'x'
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 b.x. Pero b.x aún no ha sido definido. De ahí la excepción AttributeError.
Al menos, una de las soluciones para este problema es bastante trivial. Simplemente cambia b.py para importar a.py dentro de g():
x = 1
def g():
import a # Esto se evaluará solo cuando se llame a g()
print(a.f())
Ahora, cuando lo importamos, todo está bien:
import b
b.g()
# 1 Impreso una primera vez porque el módulo 'a' llama a 'print(f())' al final
# 1 Impreso una segunda vez, esta es nuestra llamada a 'g()'
9.2 Conflicto de nombres con módulos de la biblioteca estándar de Python
Una de las maravillas de Python es la multitud de módulos que vienen "de serie". Pero como resultado, si no tienes cuidado, puedes encontrarte con que el nombre de tu módulo pueda coincidir con el nombre de un módulo de la biblioteca estándar que se entrega con Python (por ejemplo, puede que en tu código haya un módulo llamado email.py que conflictúe con el módulo de la biblioteca estándar con el mismo nombre).
Esto puede llevar a serios problemas. Por ejemplo, si alguno de los módulos intenta importar la versión del módulo de la biblioteca estándar de Python, y tú tienes un módulo en tu proyecto con el mismo nombre, por error importará tu módulo en lugar del de la biblioteca estándar.
Por lo tanto, debes ser cauteloso para 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 de un módulo en tu proyecto que presentar una solicitud para cambiar el nombre de un módulo en la biblioteca estándar y esperar su aprobación.
9.3 Visibilidad de excepciones
Considera el siguiente archivo main.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("1"))
except KeyError as e:
print('key error')
except ValueError as e:
print('value error')
print(e)
bad()
Parece correcto, el código debería funcionar, veamos qué nos imprime:
$ python main.py 1
Traceback (most recent call last):
File "C:\Projects\Python\TinderBolt\main.py", line 19, in <module>
bad()
File "C:\Projects\Python\TinderBolt\main.py", line 17, in bad
print(e)
^
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value
¿Qué acaba de pasar aquí? El problema es que en Python un objeto en un bloque de excepción no está disponible fuera de su bloque. (La razón de esto es que de lo contrario los objetos en este bloque se mantendrían en memoria hasta que el recolector de basura se ejecute y elimine las referencias a ellos).
Una forma de evitar este problema es guardar una referencia al objeto del bloque de excepción fuera de ese bloque, para que permanezca disponible. Aquí está la versión del ejemplo anterior que usa esta técnica, haciendo el código funcional:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def good():
exception = None
try:
bar(int("1"))
except KeyError as e:
exception = e
print('key error')
except ValueError as e:
exception = e
print('value error')
print(exception)
good()
9.4 Uso incorrecto del método __del__
Cuando el intérprete elimina un objeto, verifica si este objeto tiene una función __del__, y si la tiene, la llama antes de eliminar el objeto. Esto es muy útil cuando quieres que tu objeto limpie detrás de sí algunos recursos externos o caché.
Supongamos que tienes un archivo como este mod.py:
import foo
class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)
Y estás intentando hacer algo como esto desde otro archivo another_mod.py:
import mod
mybar = mod.Bar()
Y obtienes un horrible AttributeError.
¿Por qué? Porque, como se informa aquí, cuando el intérprete está cerrando, todas las variables globales del módulo tienen el valor None. Como resultado, en el ejemplo anterior, en el momento de la llamada a __del__, el nombre foo ya había sido establecido en None.
La solución a este "reto con un asterisco" será usar el método especial atexit.register(). De esta manera, cuando tu programa termine de ejecutarse (es decir, cuando salga normalmente), tus handles se eliminarán antes de que el intérprete termine de ejecutar.
Con esto en mente, la corrección para el código anterior mod.py podría lucir de la siguiente manera:
import foo
import atexit
def cleanup(handle):
foo.cleanup(handle)
class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)
Esta implementación proporciona una manera sencilla y confiable de llamar a cualquier limpieza necesaria después de la terminación normal del programa. Obviamente, la decisión sobre qué hacer con el objeto que está asociado con el nombre self.myhandle recae sobre foo.cleanup, pero, supongo que captas la idea.
GO TO FULL VERSION