Importaciones circulares (o cíclicas) en Python

¿Qué pasará si dos módulos se importan entre sí?

Para generalizar el problema, ¿qué pasa con las importaciones cíclicas en Python?

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 sentencias ejecutables. Se ejecutan cuando el progtwig 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 se haya completado.

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__, al importar el script con su propio nombre se creará un nuevo módulo no relacionado con __main__.

Tome ese lote juntos y no debería recibir ninguna sorpresa al importar módulos.

Si import foo inside bar y la import bar dentro de foo , funcionará bien. En el momento en que 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.

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 a in ), el intérprete de Python no importa b nuevamente, porque ya existe en el módulo dict.

Si intenta acceder a bx desde a bx de módulo durante la inicialización, obtendrá un AttributeError .

Agregue la siguiente línea a a.py :

 print bx 

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  import b File "/home/shlomme/tmp/x/b.py", line 2, in  import a File "/home/shlomme/tmp/x/a.py", line 7, in  print bx 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 .

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 statement 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 se pueden evitar si desea positivamente que sus importaciones estén en primer lugar :

Considere esta importación circular:

App 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 levanta, porque ya está importado, lo sacará de importcache.

PD: Tienes que leer este post completo en la voz de David Beazley.

¡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  import foo File "/home/xolve/foo.py", line 1, in  import bar File "/home/xolve/bar.py", line 1, in  from foo import gX ImportError: cannot import name gX 

Estoy completamente de acuerdo con la respuesta del python 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!

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” se generará:

 >>> '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 una salida infinita debido a la importación circular. Lo que sucede línea por línea mientras se ejecuta el “Módulo a” se muestra aquí:

  1. La primera línea es import b . por lo que visitará el módulo b
  2. La primera línea en el módulo b es import a . por lo que visitará el módulo a
  3. 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" .
  4. 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"
  5. Las líneas del módulo b se ejecutan completamente. así que volveremos al módulo a donde comenzamos el módulo b.
  6. 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 progtwig.

Las importaciones circulares pueden ser confusas porque la importación hace dos cosas:

  1. Ejecuta código de módulo importado.
  2. 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 statement 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 statement 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 ba {}'.format(hasattr(b, 'y'), a is ba) 

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 ba True # all b objects are available 

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 (bx) if __name__ == "__main__": main () 

b.py:

 import a print ("b out") x = 3 + ay 

La salida que obtengo es

 >>> b out >>> a out >>> 5 

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 

Ok, creo que tengo una solución bastante buena. Digamos que usted 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, al final 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 Luego , 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