¿Qué sucederá si dos módulos se importan entre sí?
Para generalizar el problema, ¿qué pasa con las importaciones cíclicas en Python?
¿Qué sucederá si dos módulos se importan entre sí?
Para generalizar el problema, ¿qué pasa con las importaciones cíclicas en Python?
Respuestas:
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.
Si lo haces por import foo
dentro bar
y por import bar
dentro 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 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 pueda importarse.
from foo import *
y from bar import *
también funcionará bien.
from x import y
, y aún así recibe el error de importación circular
import
se ejecute la instrucción. Por lo tanto, no se producirá un error, pero es posible que no obtenga todas las variables que espera.
from foo import *
y from bar import *
, todo lo ejecutado en el foo
se encuentra en la fase de inicialización de bar
, y las funciones reales en bar
aún no se ha definido ...
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 b
nuevamente, porque ya existe en el módulo dict.
Si intenta acceder b.x
desde la a
inicializació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.x
se accede, la línea x = 3
aún no se ha ejecutado, lo que solo sucederá después b out
.
__name__
lugar de 'a'
. Al principio, estaba totalmente confundido por qué un archivo se ejecutaría dos veces.
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 ImportError
errores, 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 quieres positivamente sus importaciones en la parte superior :
Considere esta importación circular:
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
# 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: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 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.
¡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
import bar
en foo.py
el final
bar
y foo
ambos deben usar gX
, la solución 'más limpia' es colocar gX
otro módulo y tener ambos foo
e bar
importar ese módulo. (más limpio en el sentido de que no hay dependencias semánticas ocultas).
bar
ni siquiera puede encontrar gX
en el foo. la importación circular está bien por sí misma, pero es solo que gX
no está definida cuando se importa.
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í:
import b
. entonces visitará el módulo bimport a
. entonces visitará el módulo aimport b
pero 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"
."This is from module b"
"This is from module a"
y se terminará el programa.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!
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_result
mé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 staticmethod
decorador 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.
Las importaciones circulares pueden ser confusas porque la importación hace dos cosas:
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
Resolví el problema de la siguiente manera, y funciona bien sin ningún error. Considere dos archivos a.py
y b.py
.
Agregué esto a.py
y funcionó.
if __name__ == "__main__":
main ()
import b
y = 2
def main():
print ("a out")
print (b.x)
if __name__ == "__main__":
main ()
import a
print ("b out")
x = 3 + a.y
La salida que obtengo es
>>> b out
>>> a out
>>> 5
Ok, creo que tengo una solución genial. Digamos que tiene archivo a
y archivo b
. Usted tiene una def
o class
en el archivo b
que desea utilizar en el módulo a
, pero hay algo más, o bien una def
, class
o una variable de archivo a
que 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 a
que se necesita en el archivo b
, pero antes de llamar a la función o clase desde el archivo b
que necesita para el archivo a
, diga import b
Entonces, y aquí está la parte clave , en todas las definiciones o clases en el archivo b
que necesitan el archivo def
o class
desdea
(llamémoslo CLASS
), dicesfrom a import CLASS
Esto funciona porque puede importar archivos b
sin que Python ejecute ninguna de las declaraciones de importación en el archivo b
y, por lo tanto, elude las importaciones circulares.
Por ejemplo:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
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
from a import CLASS
en 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 a
en cualquier parte de b.py.