Importaciones circulares(o cíclicas) en Python
circular-dependency cyclic-reference (11)
¡Tengo un ejemplo aquí que me llamó la atención!
foo.py
import bar
class gX(object):
g = 10
bar.py
from foo import gX
o = gX()
main.py
import foo
import bar
print "all done"
En la línea de comando: $ python main.py
Traceback (most recent call last):
File "m.py", line 1, in <module>
import foo
File "/home/xolve/foo.py", line 1, in <module>
import bar
File "/home/xolve/bar.py", line 1, in <module>
from foo import gX
ImportError: cannot import name gX
¿Qué pasará si dos módulos se importan entre sí?
Para generalizar el problema, ¿qué pasa con las importaciones cíclicas en Python?
Como otras respuestas describen este patrón es aceptable en python:
def dostuff(self):
from foo import bar
...
Lo que evitará la ejecución de la declaración de importación cuando el archivo sea importado por otros módulos. Solo si hay una dependencia circular lógica, esto fallará.
La mayoría de las importaciones circulares no son en realidad importaciones circulares lógicas, sino que ImportError
errores ImportError
, debido a la forma en que import()
evalúa las declaraciones de nivel superior de todo el archivo cuando se llama.
Estos ImportErrors
casi siempre pueden evitarse si desea positivamente que sus importaciones estén en primer lugar :
Considera esta importación circular:
Aplicación A
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
App B
# images/serializers.py
from profiles.serializers import SimplifiedProfileSerializer
class SimplifiedImageSerializer(serializers.Serializer):
title = serializers.CharField()
class ImageSerializer(SimplifiedImageSerializer):
profile = SimplifiedProfileSerializer()
De David Beazleys excelentes módulos y paquetes de conversación : ¡Vive y deja morir! - PyCon 2015 , 1:54:00
, aquí hay una manera de lidiar con las importaciones circulares en python:
try:
from images.serializers import SimplifiedImageSerializer
except ImportError:
import sys
SimplifiedImageSerializer = sys.modules[__package__ + ''.SimplifiedImageSerializer'']
Esto intenta importar SimplifiedImageSerializer
y si ImportError
se ImportError
, porque ya está importado, lo extraerá del caché de importación.
PD: Tienes que leer este post completo en la voz de David Beazley.
Esta podría ser otra solución, funcionó para mí.
def MandrillEmailOrderSerializer():
from sastaticketpk.apps.flights.api.v1.serializers import MandrillEmailOrderSerializer
return MandrillEmailOrderSerializer
email_data = MandrillEmailOrderSerializer()(order.booking).data
Estoy completamente de acuerdo con la respuesta del pitón aquí. Pero me he topado con un código que estaba defectuoso con las importaciones circulares y causó problemas al intentar agregar pruebas unitarias. Entonces, para parchearlo rápidamente sin cambiar todo, puede resolver el problema realizando una importación dinámica.
# Hack to import something without circular import issue
def load_module(name):
"""Load module using imp.find_module"""
names = name.split(".")
path = None
for name in names:
f, path, info = imp.find_module(name, path)
path = [path]
return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")
Nuevamente, esto no es una solución permanente, pero puede ayudar a alguien que quiera arreglar un error de importación sin cambiar demasiado el código.
¡Aclamaciones!
Hubo una muy buena discusión sobre esto en comp.lang.python el año pasado. Responde tu pregunta bastante a fondo.
Las importaciones son bastante sencillas en realidad. Solo recuerda lo siguiente:
''import'' y ''desde xxx import yyy'' son instrucciones ejecutables. Se ejecutan cuando el programa en ejecución llega a esa línea.
Si un módulo no está en sys.modules, entonces una importación crea la nueva entrada del módulo en sys.modules y luego ejecuta el código en el módulo. No devuelve el control al módulo de llamada hasta que la ejecución haya finalizado.
Si un módulo existe en sys.modules, una importación simplemente devuelve ese módulo, haya completado o no la ejecución. Esa es la razón por la que las importaciones cíclicas pueden devolver módulos que parecen estar parcialmente vacíos.
Finalmente, el script de ejecución se ejecuta en un módulo llamado __main__, importando el script con su propio nombre creará un nuevo módulo no relacionado con __main__.
Tome ese lote juntos y no debería tener ninguna sorpresa al importar módulos.
Las importaciones cíclicas terminan, pero debe tener cuidado de no utilizar los módulos importados cíclicamente durante la inicialización del módulo.
Considere los siguientes archivos:
a.py:
print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"
b.py:
print "b in"
import a
print "b out"
x = 3
Si ejecuta a.py, obtendrá lo siguiente:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out
En la segunda importación de b.py (en la segunda entrada), el intérprete de Python no importa b
nuevamente, porque ya existe en el módulo dict.
Si intenta acceder a bx
desde a
inicialización de módulo durante la inicialización, obtendrá un AttributeError
.
Agregue la siguiente línea a a.py
:
print b.x
Entonces, la salida es:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
File "a.py", line 4, in <module>
import b
File "/home/shlomme/tmp/x/b.py", line 2, in <module>
import a
File "/home/shlomme/tmp/x/a.py", line 7, in <module>
print b.x
AttributeError: ''module'' object has no attribute ''x''
Esto se debe a que los módulos se ejecutan al importar y en el momento en que se accede a bx
, la línea x = 3
aún no se ha ejecutado, lo que solo ocurrirá después de b out
.
Las importaciones circulares pueden ser confusas porque la importación hace dos cosas:
- Ejecuta código de módulo importado.
- agrega módulo importado a la tabla de símbolos globales del módulo de importación
Lo primero se hace solo una vez, mientras que lo último en cada declaración de importación. La importación circular crea una situación cuando el módulo de importación usa uno importado con código parcialmente ejecutado. En consecuencia, no verá los objetos creados después de la declaración de importación. El siguiente ejemplo de código lo demuestra.
Las importaciones circulares no son el mal final que debe evitarse a toda costa. En algunos marcos como Flask, son bastante naturales y ajustar su código para eliminarlos no mejora el código.
main.py
print ''import b''
import b
print ''a in globals() {}''.format(''a'' in globals())
print ''import a''
import a
print ''a in globals() {}''.format(''a'' in globals())
if __name__ == ''__main__'':
print ''imports done''
print ''b has y {}, a is b.a {}''.format(hasattr(b, ''y''), a is b.a)
b.by
print "b in, __name__ = {}".format(__name__)
x = 3
print ''b imports a''
import a
y = 5
print "b out"
a.py
print ''a in, __name__ = {}''.format(__name__)
print ''a imports b''
import b
print ''b has x {}''.format(hasattr(b, ''x''))
print ''b has y {}''.format(hasattr(b, ''y''))
print "a out"
Python main.py salida con comentarios
import b
b in, __name__ = b # b code execution started
b imports a
a in, __name__ = a # a code execution started
a imports b # b code execution is already in progress
b has x True
b has y False # b defines y after a import,
a out
b out
a in globals() False # import only adds a to main global symbol table
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available
Ok, creo que tengo una solución bastante buena. Digamos que tiene a
archivo y a
archivo b
. Tiene una def
o una class
en el archivo b
que desea usar en el módulo a
, pero tiene otra cosa, ya sea una def
, una class
o una variable del archivo a
que necesita en su definición o clase en el archivo b
. Lo que puede hacer es, en la parte inferior del archivo a
, después de llamar a la función o clase en el archivo a
que se necesita en el archivo b
, pero antes de llamar a la función o clase del archivo b
que necesita para el archivo a
, por ejemplo import b
Entonces , y aquí está la parte clave , en todas las definiciones o clases en el archivo b
que necesitan la def
o class
del archivo a
(llamémoslo CLASS
), dice from a import CLASS
Esto funciona porque puede importar el archivo b
sin que Python ejecute cualquiera de las declaraciones de importación en el archivo b
, y por lo tanto eludirá cualquier importación circular.
Por ejemplo:
Presentar un:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
Archivo b:
class B(object):
def __init__(self, number):
self.number = number
def dostuff(self):
from a import CLASS
print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."
Voila
Resolví el problema de la siguiente manera, y funciona bien sin ningún error. Considere dos archivos a.py
y b.py
a.py
esto a a.py
y funcionó.
if __name__ == "__main__":
main ()
a.py:
import b
y = 2
def main():
print ("a out")
print (b.x)
if __name__ == "__main__":
main ()
b.py:
import a
print ("b out")
x = 3 + a.y
La salida que obtengo es
>>> b out
>>> a out
>>> 5
Módulo a.py:
import b
print("This is from module a")
Módulo b.py
import a
print("This is from module b")
Ejecutando el "Módulo a" saldrá:
>>>
''This is from module a''
''This is from module b''
''This is from module a''
>>>
Dio salida a estas 3 líneas, mientras que se suponía que debía generar un infinitivo debido a la importación circular. Lo que sucede línea por línea mientras se ejecuta el "Módulo a" se muestra aquí:
- La primera línea es
import b
. por lo que visitará el módulo b - La primera línea en el módulo b es
import a
. por lo que visitará el módulo a - La primera línea en el módulo a es
import b
pero tenga en cuenta que esta línea ya no se ejecutará nuevamente , ya que cada archivo en Python ejecuta una línea de importación solo por una vez, no importa dónde o cuándo se ejecute. pasará a la siguiente línea e imprimirá"This is from module a"
. - Después de terminar de visitar todo el módulo a del módulo b, todavía estamos en el módulo b. así que la siguiente línea imprimirá
"This is from module b"
- Las líneas del módulo b se ejecutan completamente. así que volveremos al módulo a donde comenzamos el módulo b.
- La línea import b se ha ejecutado ya y no se ejecutará de nuevo. la siguiente línea imprimirá
"This is from module a"
y se terminará el programa.
Si import foo
inside bar
y la import bar
dentro de foo
, funcionará bien. Para cuando se ejecute algo, ambos módulos estarán completamente cargados y tendrán referencias entre sí.
El problema es cuando en cambio lo haces from foo import abc
y from bar import xyz
. Porque ahora cada módulo requiere que el otro módulo ya esté importado (para que exista el nombre que estamos importando) antes de que se pueda importar.