¿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.
fuente
Si lo haces por
import foo
dentrobar
y porimport bar
dentrofoo
, 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
yfrom 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.fuente
from foo import *
yfrom bar import *
también funcionará bien.from x import y
, y aún así recibe el error de importación circularimport
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 *
yfrom bar import *
, todo lo ejecutado en elfoo
se encuentra en la fase de inicialización debar
, y las funciones reales enbar
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:
b.py:
Si ejecuta a.py, obtendrá lo siguiente:
En la segunda importación de b.py (en la segunda
a in
), el intérprete de Python no importab
nuevamente, porque ya existe en el módulo dict.Si intenta acceder
b.x
desde laa
inicialización del módulo, obtendrá unAttributeError
.Agregue la siguiente línea a
a.py
:Entonces, la salida es:
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íneax = 3
aún no se ha ejecutado, lo que solo sucederá despuésb out
.fuente
__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:
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 queimport()
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:
App A
App B
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:Esto intenta importar
SimplifiedImageSerializer
y siImportError
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.
fuente
¡Tengo un ejemplo aquí que me llamó la atención!
foo.py
bar.py
main.py
En la línea de comando: $ python main.py
fuente
import bar
enfoo.py
el finalbar
yfoo
ambos deben usargX
, la solución 'más limpia' es colocargX
otro módulo y tener ambosfoo
ebar
importar ese módulo. (más limpio en el sentido de que no hay dependencias semánticas ocultas).bar
ni siquiera puede encontrargX
en el foo. la importación circular está bien por sí misma, pero es solo quegX
no está definida cuando se importa.Módulo a.py:
Módulo b.py
Ejecutar "Módulo a" generará:
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.fuente
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.
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!
fuente
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
Archivo b.py
En este caso, solo mueve un método estático a un archivo separado, por ejemplo
c.py
:Archivo c.py
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
Archivo refactorizado b.py
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.fuente
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
b. por
a.py
Python main.py salida con comentarios
fuente
Resolví el problema de la siguiente manera, y funciona bien sin ningún error. Considere dos archivos
a.py
yb.py
.Agregué esto
a.py
y funcionó.a.py:
b.py:
La salida que obtengo es
fuente
Ok, creo que tengo una solución genial. Digamos que tiene archivo
a
y archivob
. Usted tiene unadef
oclass
en el archivob
que desea utilizar en el móduloa
, pero hay algo más, o bien unadef
,class
o una variable de archivoa
que usted necesita en su definición o clase en el archivob
. Lo que puede hacer es, al final del archivoa
, después de llamar a la función o clase en el archivoa
que se necesita en el archivob
, pero antes de llamar a la función o clase desde el archivob
que necesita para el archivoa
, digaimport b
Entonces, y aquí está la parte clave , en todas las definiciones o clases en el archivob
que necesitan el archivodef
oclass
desdea
(llamémosloCLASS
), dicesfrom a import CLASS
Esto funciona porque puede importar archivos
b
sin que Python ejecute ninguna de las declaraciones de importación en el archivob
y, por lo tanto, elude las importaciones circulares.Por ejemplo:
Presentar un:
Archivo b:
Voila
fuente
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) Enimport 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 ago.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 usaraimport a
en cualquier parte de b.py.