Para comprender las dependencias circulares, debe recordar que Python es esencialmente un lenguaje de secuencias de comandos. La ejecución de declaraciones fuera de los métodos se produce en tiempo de compilación. Las instrucciones de importación se ejecutan como llamadas a métodos, y para comprenderlas, debe 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. Si no es así, 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 se han visto o el compilador 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 compila el archivo X.py. El compilador comienza definiendo el método X1 y luego ingresa a la declaración de importación en X.py. Esto hace que el compilador pause la compilación de X.py y comience a compilar Y.py. Poco después, el compilador accede a la declaración de importación en Y.py. Dado que X.py ya está en la tabla de módulos, Python usa la tabla de símbolos X.py incompleta existente para satisfacer las referencias solicitadas. Todos los símbolos que aparecen antes de la declaración de importación en X.py están ahora 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 importa correctamente. Python luego reanuda la compilación de 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 Y.py. La compilación finalmente se completa sin errores.
Algo muy diferente sucede si intenta compilar Y.py desde la línea de comandos. Mientras compila Y.py, el compilador llega 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, independientemente del archivo que 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
Siempre que crea que puede haber una importación circular, también debe evitar compilar referencias de tiempo a variables en otros módulos. Considere el código de apariencia inocente:
import X
z = X.Y
Suponga que el módulo X importa este módulo antes de que este módulo importe X. Además, suponga que Y se define 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, 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 declaración 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 declaración de importación nunca fallan. Las definiciones posteriores a 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 no se necesite ninguno de los símbolos importados 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. Compensa esto con un comentario en la parte superior de tu 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.