¿Por qué las importaciones circulares aparentemente funcionan más arriba en la pila de llamadas pero luego generan un ImportError más abajo?

Resuelto CpILL asked hace 10 años • 8 respuestas

Recibo este error

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

y puedes ver que uso la misma declaración de importación más arriba y funciona. ¿Existe alguna regla no escrita sobre la importación circular? ¿Cómo uso la misma clase más abajo en la pila de llamadas?


Consulte también ¿Qué sucede cuando se utilizan importaciones mutuas o circulares (cíclicas) en Python? para obtener una descripción general de lo que está permitido y lo que causa un problema en las importaciones circulares de WRT. Consulte ¿Qué puedo hacer con "ImportError: no se puede importar el nombre X" o "AttributeError: ... (probablemente debido a una importación circular)"? para técnicas para resolver y evitar dependencias circulares.

CpILL avatar Mar 05 '14 09:03 CpILL
Aceptado

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

La forma más sencilla de hacerlo es utilizar import my_modulela sintaxis, en lugar de from my_module import some_object. El primero casi siempre funcionará, incluso si my_modulenos importa incluido. Este último solo funciona si my_objectya está definido en my_module, lo que en una importación circular puede no ser el caso.

Para ser específico en su caso: intente cambiar entities/post.pya hacer import physicsy luego consulte physics.PostBodyen lugar de hacerlo PostBodydirectamente. De manera similar, cambie physics.pya do import entities.posty luego use entities.post.Posten lugar de simplemente Post.

Blckknght avatar Mar 05 '2014 22:03 Blckknght

Cuando importa un módulo (o un miembro de él) por primera vez, el código dentro del módulo se ejecuta secuencialmente como cualquier otro código; por ejemplo, no se trata de manera diferente que el cuerpo de una función. An importes solo un comando como cualquier otro (asignación, llamada a función, def, class). Suponiendo que sus importaciones ocurren en la parte superior del script, esto es lo que sucede:

  • Cuando intenta importar Worlddesde world, el worldscript se ejecuta.
  • El worldscript importa Field, lo que hace que el entities.fieldscript se ejecute.
  • Este proceso continúa hasta llegar al entities.postscript porque intentaste importarPost
  • El entities.postscript hace que physicsel módulo se ejecute porque intenta importarPostBody
  • Finalmente, physicsintenta importar Postdesdeentities.post
  • No estoy seguro de si el entities.postmódulo existe todavía en la memoria, pero realmente no importa. O el módulo no está en la memoria o el módulo aún no tiene un Postmiembro porque no ha terminado de ejecutarse para definirPost
  • De cualquier manera, se produce un error porque Postno está allí para ser importado.

Entonces no, no está "trabajando más arriba en la pila de llamadas". Este es un seguimiento de la pila de dónde ocurrió el error, lo que significa que se produjo un error al intentar importar Posten esa clase. No deberías utilizar importaciones circulares. En el mejor de los casos, tiene un beneficio insignificante (normalmente, ningún beneficio) y causa problemas como este. Es una carga para cualquier desarrollador que lo mantenga, obligándolo a caminar sobre cáscaras de huevo para evitar romperlo. Refactorice la organización de su módulo.

jpmc26 avatar Mar 05 '2014 02:03 jpmc26

Para comprender las dependencias circulares, debe recordar que Python es esencialmente un lenguaje de programación. La ejecución de declaraciones fuera de los métodos ocurre en tiempo de compilación. Las declaraciones de importación se ejecutan como llamadas a métodos y, para comprenderlas, debes pensar en ellas 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 es así, Python usa lo que esté actualmente en la tabla de símbolos. De lo contrario, Python comienza a leer el archivo del módulo, compilando/ejecutando/importando lo que encuentre allí. Los símbolos a los que se hace referencia en el momento de la compilación se encuentran o no, dependiendo de si el compilador los ha visto o 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 supongamos que compila el archivo X.py. El compilador comienza definiendo el método X1 y luego accede a la declaración de importación en X.py. Esto hace que el compilador detenga la compilación de X.py y comience a compilar Y.py. Poco después, el compilador llega a la declaración de importación en Y.py. Dado que X.py ya está en la tabla de módulos, Python utiliza la tabla de símbolos X.py incompleta existente para satisfacer cualquier referencia solicitada. Todos los símbolos que aparecen antes de la declaración de importación en X.py ahora están en la tabla de símbolos, pero los símbolos posteriores no. Dado que X1 ahora aparece antes de la declaración de importación, se importó correctamente. Luego, Python continúa compilando Y.py. Al hacerlo, define Y2 y termina de compilar Y.py. Luego reanuda la compilación de X.py y encuentra Y2 en la tabla de símbolos de Y.py. La compilación finalmente se completa sin errores.

Algo muy diferente sucede si intentas compilar Y.py desde la línea de comando. Mientras compila Y.py, el compilador accede a la declaración de importación antes de definir Y2. Luego comienza a compilar X.py. Pronto llega a la declaración de importación en X.py que requiere Y2. Pero Y2 no está definido, por lo que la compilación falla.

Tenga en cuenta que si modifica X.py para importar Y1, la compilación siempre se realizará correctamente, sin importar qué archivo compile. Sin embargo, si modifica el archivo Y.py para importar el símbolo X2, ninguno de los archivos se compilará.

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

from X import Y

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

import X
z = X.Y

Supongamos que el módulo X importa este módulo antes de que este módulo importe X. Además, supongamos que Y está definido en X después de la declaración de importación. Entonces Y no se definirá cuando se importe este módulo y obtendrá un error de compilación. Si este módulo importa Y primero, puedes salirte con la tuya. 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 declaración de importación debajo de las definiciones de símbolos que necesitan otros módulos. En los ejemplos anteriores, las definiciones antes de la declaración de importación nunca fallan. Las definiciones después de la declaración de importación a veces fallan, según el orden de compilación. Incluso puede colocar declaraciones de importación al final de un archivo, siempre que ninguno de los símbolos importados sea necesario en el momento de la compilación.

Tenga en cuenta que mover las declaraciones de importación hacia abajo en un módulo oscurece lo que está haciendo. Compense esto con un comentario en la parte superior de su módulo, similar al 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 evitarla.

Gene Olson avatar Oct 17 '2016 19:10 Gene Olson

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

"...Para hacer referencia a modelos definidos en otra aplicación, puede especificar explícitamente un modelo con la etiqueta de 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 resultar útil al resolver dependencias de importación circular entre dos aplicaciones. ..."

Malik A. Rumi avatar Sep 26 '2016 20:09 Malik A. Rumi