python circular-dependency cyclic-reference

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:

  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 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í:

  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 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.