¿Qué sucede cuando se utilizan importaciones mutuas o circulares (cíclicas)?

14 minutos de lectura

Avatar de usuario de Xolve
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.

  • 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 AttributeErrors – permite buscar el módulo parcialmente inicializado en sys.modulespero no resuelve las paradojas del tiempo.

    – Karl Knechtel

    11 ago a las 4:00

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 * y from 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 yy 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 * y from bar import *todo ejecutado en el foo está en la fase de inicialización de bary las funciones reales en bar aun no se ha definido…

    – MarcianoMarciano

    18 de enero de 2017 a las 2:37


Avatar de usuario de Shane C. Mason
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

Avatar de usuario de Torsten Marek
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:00aquí 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.

Avatar de usuario de Iterniam
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.

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

  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á 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".
  4. 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"
  5. Las líneas del módulo b se ejecutan completamente. entonces regresaremos al módulo a donde comenzamos el módulo b.
  6. 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.

Avatar de usuario de Moh
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 en foo.py hasta el final

    – warvariuc

    5 de agosto de 2013 a las 9:52

  • Si bar y foo ambos deben usar gXla solución ‘más limpia’ es poner gX en otro módulo y tener ambos foo y bar 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 encontrar gX en el pie la importación circular está bien por sí sola, pero es solo que gX no está definido cuando se importa.

    – MarcianoMarciano

    18 de enero de 2017 a las 3:31

¿Ha sido útil esta solución?