Python circular de importación?

Así que estoy recibiendo este error

Traceback (most recent call last): File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in  from world import World File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in  from entities.field import Field File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in  from entities.goal import Goal File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in  from entities.post import Post File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in  from physics import PostBody File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in  from entities.post import Post ImportError: cannot import name Post 

¿Y puedes ver que uso la misma statement de importación más arriba y funciona? ¿Hay alguna regla no escrita sobre la importación circular? ¿Cómo uso la misma clase más abajo en la stack de llamadas?

Creo que la respuesta de jpmc26, aunque de ninguna manera es incorrecta , se basa demasiado en las importaciones circulares. Pueden funcionar bien, si los configura correctamente.

La forma más sencilla de hacerlo es usar la syntax import my_module , en lugar from my_module import some_object . El primero casi siempre funcionará, incluso si my_module included nos vuelve a importar. Este último solo funciona si my_object ya está definido en my_module , lo que en una importación circular puede no ser el caso.

Para ser específico a su caso: intente cambiar entities/post.py para import physics y luego physics.PostBody referencia a physics.PostBody lugar de solo PostBody directamente. De manera similar, cambie physics.py para import entities.post physics.py y luego utilice physics.py en lugar de solo Post .

Cuando importa un módulo (o un miembro de él) por primera vez, el código dentro del módulo se ejecuta de forma secuencial como cualquier otro código; por ejemplo, no se trata de manera diferente que el cuerpo de una función. Una import es solo un comando como cualquier otro (asignación, una llamada de función, def , class ). Asumiendo que sus importaciones ocurren en la parte superior del script, entonces esto es lo que está sucediendo:

  • Cuando intentas importar World from world , se ejecuta el script world .
  • El world importa el Field secuencia de comandos, que hace que se ejecute la secuencia de las entities.field .
  • Este proceso continúa hasta que llegue a la secuencia de las entities.post .post porque intentó importar la Post
  • El script entities.post hace que el módulo de physics se ejecute porque intenta importar PostBody
  • Finalmente, la physics trata de importar Post de entities.post
  • No estoy seguro de si el módulo entities.post existe todavía en la memoria, pero realmente no importa. O bien el módulo no está en la memoria, o el módulo aún no tiene un miembro de Post porque no ha terminado de ejecutarse para definir Post
  • De cualquier manera, se produce un error porque la Post no está allí para ser importada

Así que no, no está “trabajando más arriba en la stack de llamadas”. Esta es una traza de stack de donde ocurrió el error, lo que significa que falló al intentar importar la Post en esa clase. No debes usar importaciones circulares. En el mejor de los casos, tiene un beneficio insignificante (por lo general, ningún beneficio) y causa problemas como este. Grava a cualquier desarrollador que lo mantenga, lo que obliga a caminar sobre shells de huevo para evitar romperlo. Refactoriza la organización de tu módulo.

Para comprender las dependencias circulares, debe recordar que Python es esencialmente un lenguaje de scripting. La ejecución de sentencias fuera de los métodos se produce en tiempo de comstackción. Las instrucciones de importación se ejecutan igual que las llamadas a métodos, y para entenderlas, debe pensarlas como llamadas a métodos.

Cuando realiza una importación, lo que sucede depende de si el archivo que está importando ya existe en la tabla del módulo. Si lo hace, Python usa lo que está actualmente en la tabla de símbolos. Si no, Python comienza a leer el archivo del módulo, comstackndo / ejecutando / importando lo que encuentre allí. Los símbolos a los que se hace referencia en el momento de la comstackción se encuentran o no, dependiendo de si se han visto o si el comstackdor aún no los ha visto.

Imagina que tienes dos archivos fuente:

Archivo X.py

 def X1: return "x1" from Y import Y2 def X2: return "x2" 

Archivo Y.py

 def Y1: return "y1" from X import X1 def Y2: return "y2" 

Ahora suponga que comstack el archivo X.py. El comstackdor comienza definiendo el método X1 y luego llega a la statement de importación en X.py. Esto hace que el comstackdor haga una pausa en la comstackción de X.py y comience a comstackr Y.py. Poco después, el comstackdor llega a la statement de importación en Y.py. Como X.py ya está en la tabla del módulo, Python usa la tabla de símbolos X.py incompleta existente para satisfacer todas las referencias solicitadas. Todos los símbolos que aparecen antes de la statement de importación en X.py ahora están en la tabla de símbolos, pero los símbolos posteriores no lo están. Como X1 ahora aparece antes de la statement de importación, se importa con éxito. Python luego reanuda la comstackción de Y.py. Al hacerlo, define Y2 y termina de comstackr Y.py. Luego reanuda la comstackción de X.py y encuentra Y2 en la tabla de símbolos Y.py. La comstackción eventualmente termina sin error.

Algo muy diferente sucede si intentas comstackr Y.py desde la línea de comandos. Mientras comstack Y.py, el comstackdor golpea la statement de importación antes de definir Y2. Entonces comienza a comstackr X.py. Pronto llega a la statement de importación en X.py que requiere Y2. Pero Y2 no está definido, por lo que la comstackción falla.

Tenga en cuenta que si modifica X.py para importar Y1, la comstackción siempre tendrá éxito, independientemente del archivo que compile. Sin embargo, si modifica el archivo Y.py para importar el símbolo X2, ninguno de los archivos se comstackrá.

En cualquier momento en que el módulo X, o cualquier módulo importado por X pueda importar el módulo actual, NO use:

 from X import Y 

Cada vez que piense que puede haber una importación circular, también debe evitar las referencias de tiempo de comstackción a variables en otros módulos. Considere el código de aspecto inocente:

 import X z = XY 

Supongamos que el módulo X importa este módulo antes de que este módulo importe X. Además, supongamos que Y se define en X después de la statement de importación. Entonces no se definirá Y cuando se importe este módulo y obtendrá un error de comstackción. Si este módulo importa Y primero, puede salirse con la suya. Pero cuando uno de sus compañeros de trabajo cambia inocentemente el orden de las definiciones en un tercer módulo, el código se romperá.

En algunos casos, puede resolver dependencias circulares moviendo una statement de importación hacia abajo debajo de las definiciones de símbolos que necesitan otros módulos. En los ejemplos anteriores, las definiciones antes de la statement de importación nunca fallan. Las definiciones después de la statement de importación a veces fallan, según el orden de comstackción. Incluso puede poner instrucciones de importación al final de un archivo, siempre que no se necesite ninguno de los símbolos importados en el momento de la comstackción.

Tenga en cuenta que mover las declaraciones de importación hacia abajo en un módulo oculta lo que está haciendo. Compense esto con un comentario en la parte superior de su módulo, algo como lo siguiente:

 #import X (actual import moved down to avoid circular dependency) 

En general, esta es una mala práctica, pero a veces es difícil de evitar.

Para aquellos de ustedes que, como yo, vengo a este problema de Django, deben saber que los documentos proporcionan una solución: https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

“… Para referirse a los modelos definidos en otra aplicación, puede especificar explícitamente un modelo con la etiqueta de la aplicación completa. Por ejemplo, si el modelo del fabricante anterior está definido en otra aplicación llamada producción, deberá usar:

 class Car(models.Model): manufacturer = models.ForeignKey( 'production.Manufacturer', on_delete=models.CASCADE, ) 

Este tipo de referencia puede ser útil al resolver dependencias de importación circular entre dos aplicaciones. … ”

Si se encuentra con este problema en una aplicación bastante compleja, puede ser engorroso refactorizar todas sus importaciones. PyCharm ofrece una solución rápida para esto que también cambiará automáticamente todo el uso de los símbolos importados.

introduzca la descripción de la imagen aquí