Importaciones circulares (o cíclicas) en Python

353

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

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

Xolve
fuente
1
Ver también stackoverflow.com/questions/158268/…
Constantin
1
También como referencia, parece que las importaciones circulares están permitidas en Python 3.5 (y probablemente más allá) pero no 3.4 (y probablemente más abajo).
Charlie Parker
44
Estoy usando python 3.7.2 y todavía tengo un error de tiempo de ejecución debido a dependencias circulares.
Richard Whitehead

Respuestas:

282

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 realmente. Solo recuerda lo siguiente:

'import' y 'from xxx import yyy' son declaraciones ejecutables. Se ejecutan cuando el programa en ejecución llega a esa línea.

Si un módulo no está en sys.modules, 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 se haya completado la ejecución.

Si un módulo existe en sys.modules, entonces una importación simplemente devuelve ese módulo, ya sea que se haya completado o no la ejecución. Esa es la razón por la cual las importaciones cíclicas pueden devolver módulos que parecen estar parcialmente vacíos.

Finalmente, la secuencia de comandos en ejecución se ejecuta en un módulo llamado __main__, la importación de la secuencia de comandos con su propio nombre creará un nuevo módulo no relacionado con __main__.

Tome todo eso en conjunto y no debería tener sorpresas al importar módulos.

Shane C. Mason
fuente
13
@meawoppl ¿Podría ampliar este comentario, por favor? ¿Cómo han cambiado específicamente?
Dan Schien el
3
A partir de ahora, la única referencia a las importaciones circulares en python3 "¿Qué hay de nuevo?" Las páginas están en el 3.5 . Dice "Las importaciones circulares que involucran importaciones relativas ahora son compatibles". @meawoppl, ¿ha encontrado algo más que no se encuentre en estas páginas?
zezollo
44
Son def. no admitido en 3.0-3.4. O al menos la semántica para el éxito es diferente. Aquí hay una sinopsis que encontré que no menciona los cambios de 3.5. gist.github.com/datagrok/40bf84d5870c41a77dc6
meawoppl
Por favor, ¿puede ampliar esto? "Finalmente, la secuencia de comandos en ejecución se ejecuta en un módulo llamado main , la importación de la secuencia de comandos con su propio nombre creará un nuevo módulo no relacionado con main ". Entonces, digamos que el archivo es a.py y cuando se ejecuta como punto de entrada principal, ahora es el principal si tiene un código como el de una variable de importación. Entonces, ¿se cargará el mismo archivo 'a.py' en la tabla de módulos sys? Entonces, ¿significa que si tiene una declaración de impresión, se ejecutará dos veces? ¿Una vez para el archivo principal y otra vez cuando se encuentra la importación?
variable
Esta respuesta tiene 10 años, y me gustaría una actualización modernizada para garantizar que siga siendo correcta en varias versiones de Python, 2.xo 3.x
Fallenreaper el
296

Si lo haces por import foodentro bary por import bardentro foo, funcionará bien. Para cuando realmente se ejecute algo, ambos módulos estarán completamente cargados y tendrán referencias mutuas.

El problema es cuando en cambio lo haces from foo import abcy 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 pueda importarse.

user2357112 es compatible con Monica
fuente
27
Parece que from foo import *y from bar import *también funcionará bien.
Akavall
1
Verifique la edición de la publicación anterior utilizando a.py/b.py. Él no usa from x import y, y aún así recibe el error de importación circular
Greg Ennis
2
Esto no es enteramente verdad. Al igual que import * from, si intentas acceder a un elemento en la importación circular, en el nivel superior, antes de que el script complete su ejecución, tendrás el mismo problema. Por ejemplo, si está configurando un paquete global en un paquete de otro, y ambos se incluyen entre sí. Estaba haciendo esto para crear una fábrica descuidada para un objeto en la clase base donde ese objeto podría ser uno de una serie de subclases y el código de uso no necesitaba ser consciente de lo que realmente estaba creando.
AaronM
3
@Akavall No realmente. Eso solo importará los nombres que estén disponibles cuando importse ejecute la instrucción. Por lo tanto, no se producirá un error, pero es posible que no obtenga todas las variables que espera.
augurar
3
Tenga en cuenta, si lo hace from foo import *y from bar import *, todo lo ejecutado en el foose encuentra en la fase de inicialización de bar, y las funciones reales en baraún no se ha definido ...
Martian2049
100

Las importaciones cíclicas finalizan, 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 bnuevamente, porque ya existe en el módulo dict.

Si intenta acceder b.xdesde la ainicialización del módulo, 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 en la importación y en el momento en que b.xse accede, la línea x = 3aún no se ha ejecutado, lo que solo sucederá después b out.

Torsten Marek
fuente
14
Esto explica en gran medida el problema, pero ¿qué tal la solución? ¿Cómo podríamos importar e imprimir correctamente x? la otra solución anterior no funcionó para mí
mehmet
Creo que esta respuesta se beneficiaría mucho si la usaras en __name__lugar de 'a'. Al principio, estaba totalmente confundido por qué un archivo se ejecutaría dos veces.
Bergi
30

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 realmente importaciones circulares lógicas, sino que generan ImportErrorerrores, debido a la forma en que import()evalúa las declaraciones de nivel superior de todo el archivo cuando se llama.

Estos ImportErrorscasi siempre se pueden evitar si quieres positivamente sus importaciones en la parte superior :

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 excelente charla Módulos y paquetes: ¡Vive y deja morir! - PyCon 2015 , 1:54:00aquí 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 SimplifiedImageSerializery si ImportErrorse genera, porque ya está importado, lo extraerá del caché de importación.

PD: Tienes que leer esta publicación completa con la voz de David Beazley.

Sebastian Wozny
fuente
99
ImportError no se genera si el módulo ya se ha importado. Los módulos se pueden importar tantas veces como desee, es decir, "importar a; importar a;" esta bien
Yuras
9

¡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
Xolve
fuente
2
¿Cómo arreglaste esto? Estoy tratando de entender la importación circular para solucionar un problema de mi propia que se ve muy similar a lo que estás haciendo ...
C089
12
Errm ... creo que solucioné mi problema con este truco increíblemente feo. {{{si no es 'foo.bar' en sys.modules: de foo import bar else: bar = sys.modules ['foo.bar']}}} Personalmente, creo que las importaciones circulares son una ENORME señal de advertencia en código incorrecto diseño ...
C089
55
@ C089, o usted podría pasar import baren foo.pyel final
warvariuc
55
Si bary fooambos deben usar gX, la solución 'más limpia' es colocar gXotro módulo y tener ambos fooe barimportar ese módulo. (más limpio en el sentido de que no hay dependencias semánticas ocultas).
Tim Wilder
2
Tim tiene un buen punto. Básicamente es porque barni siquiera puede encontrar gXen el foo. la importación circular está bien por sí misma, pero es solo que gXno está definida cuando se importa.
Martian2049
9

Módulo a.py:

import b
print("This is from module a")

Módulo b.py

import a
print("This is from module b")

Ejecutar "Módulo a" generará:

>>> 
'This is from module a'
'This is from module b'
'This is from module a'
>>> 

Emitió estas 3 líneas mientras se suponía que debía generar infinitival debido a la importación circular. Lo que sucede línea por línea mientras se ejecuta el "Módulo a" se enumera aquí:

  1. La primera línea es import b. entonces visitará el módulo b
  2. La primera línea en el módulo b es import a. entonces visitará el módulo a
  3. La primera línea en el módulo a es import bpero tenga en cuenta que esta línea ya no se ejecutará nuevamente , porque cada archivo en python ejecuta una línea de importación solo por una vez, no importa dónde o cuándo se ejecute. entonces 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 desde el módulo b, todavía estamos en el módulo b. así se imprimirá la siguiente línea"This is from module b"
  5. Las líneas del módulo b se ejecutan por completo. entonces volveremos al módulo a donde comenzamos el módulo b.
  6. la línea de importación b ya se ha ejecutado y no se volverá a ejecutar. se imprimirá la siguiente línea "This is from module a"y se terminará el programa.
Mohsen Haddadi
fuente
4

Estoy completamente de acuerdo con la respuesta de Pythoneer aquí. Pero me topé 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 haciendo 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 corregir un error de importación sin cambiar demasiado el código.

¡Salud!

radtek
fuente
3

Hay muchas respuestas geniales aquí. Si bien generalmente hay soluciones rápidas al problema, algunas de las cuales se sienten más pitónicas que otras, si tiene el lujo de refactorizar, otro enfoque es analizar la organización de su código e intentar eliminar la dependencia circular. Puede encontrar, por ejemplo, que tiene:

Archivo a.py

from b import B

class A:
    @staticmethod
    def save_result(result):
        print('save the result')

    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Archivo b.py

from a import A

class B:
    @staticmethod
    def do_something_b_ish(param):
        A.save_result(B.use_param_like_b_would(param))

En este caso, solo mueve un método estático a un archivo separado, por ejemplo c.py:

Archivo c.py

def save_result(result):
    print('save the result')

permitirá eliminar el save_resultmétodo de A y, por lo tanto, permitirá eliminar la importación de A de a en b:

Archivo refactorizado a.py

from b import B
from c import save_result

class A:
    @staticmethod
    def do_something_a_ish(param):
        A.save_result(A.use_param_like_a_would(param))

    @staticmethod
    def do_something_related_to_b(param):
        B.do_something_b_ish(param)

Archivo refactorizado b.py

from c import save_result

class B:
    @staticmethod
    def do_something_b_ish(param):
        save_result(B.use_param_like_b_would(param))

En resumen, si tiene una herramienta (por ejemplo, pylint o PyCharm) que informa sobre métodos que pueden ser estáticos, simplemente lanzar un staticmethoddecorador sobre ellos podría no ser la mejor manera de silenciar la advertencia. Aunque el método parece estar relacionado con la clase, podría ser mejor separarlo, especialmente si tiene varios módulos estrechamente relacionados que podrían necesitar la misma funcionalidad y tiene la intención de practicar los principios DRY.

hlongmore
fuente
2

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

  1. ejecuta el código del módulo importado
  2. agrega el módulo importado a la tabla de símbolos globales del módulo de importación

El primero se realiza solo una vez, mientras que el segundo 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. Debajo de la muestra de código lo demuestra.

Las importaciones circulares no son el mal supremo 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. por

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
Jacek Błocki
fuente
1

Resolví el problema de la siguiente manera, y funciona bien sin ningún error. Considere dos archivos a.pyy b.py.

Agregué esto a.pyy 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
Irfanuddin
fuente
0

Ok, creo que tengo una solución genial. Digamos que tiene archivo ay archivo b. Usted tiene una defo classen el archivo bque desea utilizar en el módulo a, pero hay algo más, o bien una def, classo una variable de archivo aque usted 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 aque se necesita en el archivo b, pero antes de llamar a la función o clase desde el archivo bque necesita para el archivo a, diga import b Entonces, y aquí está la parte clave , en todas las definiciones o clases en el archivo bque necesitan el archivo defo classdesdea(llamémoslo CLASS), dicesfrom a import CLASS

Esto funciona porque puede importar archivos bsin que Python ejecute ninguna de las declaraciones de importación en el archivo by, por lo tanto, elude las importaciones circulares.

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

Cary Shindell
fuente
from a import CLASSen realidad no omite ejecutar todo el código en a.py. Esto es lo que realmente sucede: (1) Todo el código en a.py se ejecuta como un módulo especial "__main__". (2) En import b, se ejecuta el código de nivel superior en b.py (que define la clase B) y luego el control vuelve a "__main__". (3) "__main__" finalmente pasa el control a go.dostuff(). (4) cuando llega dostuff () import a, ejecuta todo el código en a.py nuevamente , esta vez como el módulo "a"; luego importa el objeto CLASS del nuevo módulo "a". Entonces, en realidad, esto funcionaría igual de bien si lo usara import aen cualquier parte de b.py.
Matthias Fripp