Xolve
En Python, ¿qué sucede cuando dos módulos intentan import
¿El uno al otro? En términos más generales, ¿qué sucede si varios módulos intentan import
en un ciclo?
Consulte también ¿Qué puedo hacer con “ImportError: no se puede importar el nombre X” o “AttributeError: … (probablemente debido a una importación circular)”? para el problema común que puede resultar, y consejos sobre cómo reescribir el código para evitar dichas importaciones. Consulte ¿Por qué las importaciones circulares aparentemente funcionan más arriba en la pila de llamadas pero luego generan un ImportError más abajo? para detalles técnicos sobre porque y como se produce el problema.
Si lo haces import foo
(en el interior bar.py
) y import bar
(en el interior foo.py
), funcionará bien. En el momento en que todo se ejecute realmente, 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
(en el interior bar.py
) y from bar import xyz
(en el interior foo.py
). 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.
-
Parece que
from foo import *
yfrom bar import *
también funcionará bien.– Akavall
12 mayo 2014 a las 16:54
-
Verifique la edición de la publicación anterior usando a.py/b.py. el no usa
from x import y
y aún así obtiene el error de importación circular–Greg Ennis
30 de junio de 2014 a las 14:09
-
Esto no es enteramente verdad. Al igual que import * from, si intenta acceder a un elemento en la importación circular, en el nivel superior, antes de que el script complete su ejecución, tendrá 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 una de varias subclases y el código de uso no necesitaba saber cuál estaba creando realmente.
– AaronM
13/04/2016 a las 20:54
-
@Akavall No realmente. Eso solo importará los nombres que están disponibles cuando el
import
se ejecuta la sentencia. Por lo tanto, no se producirá un error, pero es posible que no obtenga todas las variables que espera.– augurar
24 de diciembre de 2016 a las 1:41
-
Tenga en cuenta que si lo hace
from foo import *
yfrom bar import *
todo ejecutado en elfoo
está en la fase de inicialización debar
y las funciones reales enbar
aun no se ha definido…– MarcianoMarciano
18 de enero de 2017 a las 2:37
Shane C Mason
Hubo una muy buena discusión sobre esto en comp.lang.python el año pasado. Responde bastante bien a tu pregunta.
Las importaciones son bastante sencillas en realidad. Solo recuerda lo siguiente:
‘import’ y ‘from xxx import yyy’ son sentencias 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 de 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 completa la ejecución.
Si existe un módulo en sys.modules, una importación simplemente devuelve ese módulo, ya sea que haya terminado de ejecutarse o no. 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 junto y no debería llevarse ninguna sorpresa al importar módulos.
-
@meawoppl ¿Podría ampliar este comentario, por favor? ¿Cómo han cambiado específicamente?
– Dan Schien
7 abr 2016 a las 10:17
-
A partir de ahora, la única referencia a las importaciones circulares en python3 “¿Qué hay de nuevo?” páginas es en el 3.5. Dice “Las importaciones circulares que involucran importaciones relativas ahora son compatibles”. @meawoppl, ¿ha encontrado algo más que no aparezca en estas páginas?
– zezollo
21 de abril de 2016 a las 5:38
-
Ellos son definitivamente no soportado en 3.0-3.4. O al menos la semántica del éxito es diferente. Aquí hay una sinopsis que encontré que no menciona los cambios 3.5. gist.github.com/datagrok/40bf84d5870c41a77dc6
– meowoppl
22 de abril de 2016 a las 19:02
-
¿Puede ampliar este “Finalmente, el script de ejecución se ejecuta en un módulo llamado principalimportar el script con su propio nombre creará un nuevo módulo no relacionado con principal.”. Así que digamos que el archivo es a.py y cuando se ejecuta como punto de entrada principal, es el principal ahora si tiene código como de una importación alguna variable. 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
30 de agosto de 2019 a las 3:39
-
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.x o 3.x
– Segador caído
30 oct 2019 a las 20:15
Torsten Marek
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
de nuevo, porque ya existe en el módulo dict.
Si intenta acceder b.x
de a
durante la inicialización del módulo, obtendrá un AttributeError
.
Añada 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 b.x
se accede, la línea x = 3
aún no se ha ejecutado, lo que solo sucederá después de b out
.
-
esto explica en gran medida el problema, pero ¿qué hay de la solución? ¿Cómo podríamos importar e imprimir correctamente x? la otra solución anterior no funcionó para mí
– mehmet
27 de marzo de 2018 a las 18:23
-
Creo que esta respuesta se beneficiaría mucho si usaras
__name__
en vez de'a'
. Al principio, estaba totalmente confundido por qué un archivo se ejecutaría dos veces.– Bergi
18 de febrero de 2020 a las 2:46
-
@mehmet Refactorice su proyecto para que las declaraciones de importación formen una estructura similar a un árbol (el script principal importa módulos de soporte que a su vez pueden importar sus módulos de soporte, etc.). Este es el enfoque generalmente recomendable.
– Jeyekomon
06/01/2021 a las 11: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 otros módulos importen el archivo. 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 aumentan ImportError
errores, por la forma import()
evalúa declaraciones de nivel superior de todo el archivo cuando se le llama.
Estas ImportErrors
casi siempre se puede evitar si desea que sus importaciones estén en la cima:
Considere 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)
Aplicación 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:00
aquí hay una forma 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 extraerá del importcache.
PD: Tienes que leer todo este post con la voz de David Beazley.
Iterniam
Para mi sorpresa, nadie ha mencionado las importaciones cíclicas causadas por sugerencias de tipos todavía.
Si tienes importaciones cíclicas solamente como resultado de la sugerencia de tipo, se pueden evitar de manera limpia.
Considerar main.py
que hace uso de excepciones de otro archivo:
from src.exceptions import SpecificException
class Foo:
def __init__(self, attrib: int):
self.attrib = attrib
raise SpecificException(Foo(5))
Y la clase de excepción dedicada exceptions.py
:
from src.main import Foo
class SpecificException(Exception):
def __init__(self, cause: Foo):
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
Esto levantará un ImportError
ya que main.py
importaciones exception.py
y viceversa a través Foo
y SpecificException
.
Porque Foo
solo se requiere en exceptions.py
durante la verificación de tipos, podemos hacer que su importación sea condicional de manera segura usando el TYPE_CHECKING
constante de la mecanografía módulo. La constante es solo True
durante la verificación de tipos, lo que nos permite importar condicionalmente Foo
y así evitar el error de importación circular.
Algo a tener en cuenta es que al hacerlo, Foo
no se declara enExceptions.py en tiempo de ejecución, lo que conduce a un NameError
. Para evitar eso, agregamos from __future__ import annotations
que transforma todas las anotaciones de tipo en el módulo en cadenas.
Por lo tanto, obtenemos el siguiente código para Python 3.7+:
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING: # Only imports the below statements during type checking
from src.main import Foo
class SpecificException(Exception):
def __init__(self, cause: Foo): # Foo becomes 'Foo' because of the future import
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
En Python 3.6, la importación futura no existe, por lo que Foo
tiene que ser una cadena:
from typing import TYPE_CHECKING
if TYPE_CHECKING: # Only imports the below statements during type checking
from src.main import Foo
class SpecificException(Exception):
def __init__(self, cause: 'Foo'): # Foo has to be a string
self.cause = cause
def __str__(self):
return f'Expected 3 but got {self.cause.attrib}.'
En Python 3.5 y versiones anteriores, la función de sugerencia de tipo aún no existía.
En versiones futuras de Python, la función de anotaciones puede volverse obligatoria, después de lo cual ya no será necesaria la importación futura. Aún no se ha determinado en qué versión ocurrirá esto.
Esta respuesta se basa en Otra solución más para sacarlo de un agujero de importación circular en Python de Stefaan Lippens.
-
para tu información,
from __future__ import annotations
se planeó para convertirse en el predeterminado en 3.10, diferido a 3.11, y luego poner en espera indefinida debido a algunos problemas que crea que no han encontrado una buena solución para. Así que si lo quieres, vas a necesitar el__future__
importar por un tiempo.– ShadowRanger
9 de noviembre a las 15:22
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 generaría un infinitivo debido a la importación circular. Lo que sucede línea por línea mientras se ejecuta el “Módulo a” se enumera 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á de nuevo, porque cada archivo en python ejecuta una línea de importación solo una vez, no importa dónde o cuándo se ejecute. por lo que 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. por lo que la siguiente línea se imprimirá
"This is from module b"
- Las líneas del módulo b se ejecutan completamente. entonces regresaremos al módulo a donde comenzamos el módulo b.
- La línea import b ya se ha ejecutado y no se volverá a ejecutar. la siguiente línea se imprimirá
"This is from module a"
y el programa habrá terminado.
-
para tu información,
from __future__ import annotations
se planeó para convertirse en el predeterminado en 3.10, diferido a 3.11, y luego poner en espera indefinida debido a algunos problemas que crea que no han encontrado una buena solución para. Así que si lo quieres, vas a necesitar el__future__
importar por un tiempo.– ShadowRanger
9 de noviembre a las 15:22
Oficial médico
¡Aquí tengo un ejemplo que me llamó la atención!
foo.py
import bar
class gX(object):
g = 10
bar.py
from foo import gX
o = gX()
principal.py
import foo
import bar
print "all done"
En la línea de comando: $ python principal.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
-
¿Cómo arreglaste esto? Estoy tratando de entender la importación circular para solucionar un problema propio que parece muy similar a lo que estás haciendo…
– c089
9 de agosto de 2010 a las 6:53
-
Errm… Creo que solucioné mi problema con este truco increíblemente feo. {{{ si no es ‘foo.bar’ en sys.modules: from foo import bar else: bar = sys.modules[‘foo.bar’] }}} Personalmente, creo que las importaciones circulares son una ENORME señal de advertencia sobre un mal diseño de código…
– c089
9 de agosto de 2010 a las 7:01
-
@c089, o podrías moverte
import bar
enfoo.py
hasta el final– warvariuc
5 de agosto de 2013 a las 9:52
-
Si
bar
yfoo
ambos deben usargX
la solución ‘más limpia’ es ponergX
en otro módulo y tener ambosfoo
ybar
importar ese módulo. (más limpio en el sentido de que no hay dependencias semánticas ocultas).– Tim Wilder
17 dic 2013 a las 20:32
-
Tim tiene un buen punto. Básicamente es porque
bar
ni siquiera puedo encontrargX
en el pie la importación circular está bien por sí sola, pero es solo quegX
no está definido cuando se importa.– MarcianoMarciano
18 de enero de 2017 a las 3:31
Consulte también stackoverflow.com/questions/158268/…
– Constantino
14 de abril de 2009 a las 14:58
también solo como referencia, parece que las importaciones circulares están permitidas en python 3.5 (y probablemente más allá) pero no en 3.4 (y probablemente más abajo).
–Charlie Parker
8 de febrero de 2017 a las 15:17
Estoy usando python 3.7.2 y sigo teniendo un error de tiempo de ejecución debido a las dependencias circulares.
– Ricardo Whitehead
15 de marzo de 2019 a las 10:19
@CharlieParker Esto se aplica específicamente a las importaciones relativas, según Novedades en 3.5. La entrada de seguimiento de problemas relevante es aquí. También se hicieron cambios en 3.7 apoyar algunos casos de importación absoluta. Sin embargo, esto no impide
AttributeError
s – permite buscar el módulo parcialmente inicializado ensys.modules
pero no resuelve las paradojas del tiempo.– Karl Knechtel
11 ago a las 4:00