Per comprendere le dipendenze circolari, è necessario ricordare che Python è essenzialmente un linguaggio di scripting. L'esecuzione di istruzioni al di fuori dei metodi avviene in fase di compilazione. Le istruzioni di importazione vengono eseguite proprio come le chiamate di metodo e per comprenderle dovresti pensarle come chiamate di metodo.
Quando si esegue un'importazione, ciò che accade dipende dal fatto che il file che si sta importando sia già presente nella tabella del modulo. Se lo fa, Python usa tutto ciò che è attualmente nella tabella dei simboli. In caso contrario, Python inizia a leggere il file del modulo, compilando / eseguendo / importando tutto ciò che trova lì. I simboli a cui si fa riferimento in fase di compilazione vengono trovati o meno, a seconda che siano stati visti o debbano ancora essere visti dal compilatore.
Immagina di avere due file sorgente:
File X.py
def X1:
return "x1"
from Y import Y2
def X2:
return "x2"
File Y.py
def Y1:
return "y1"
from X import X1
def Y2:
return "y2"
Supponiamo ora di compilare il file X.py. Il compilatore inizia definendo il metodo X1, quindi preme l'istruzione import in X.py. Ciò fa sì che il compilatore sospenda la compilazione di X.py e inizi a compilare Y.py. Poco dopo il compilatore preme l'istruzione import in Y.py. Poiché X.py è già nella tabella del modulo, Python utilizza la tabella dei simboli X.py incompleta esistente per soddisfare qualsiasi riferimento richiesto. Tutti i simboli che compaiono prima dell'istruzione import in X.py sono ora nella tabella dei simboli, ma i simboli dopo non lo sono. Poiché X1 ora appare prima dell'istruzione import, viene importato correttamente. Python riprende quindi a compilare Y.py. In tal modo definisce Y2 e termina la compilazione di Y.py. Quindi riprende la compilazione di X.py e trova Y2 nella tabella dei simboli Y.py. La compilazione alla fine viene completata senza errori.
Qualcosa di molto diverso accade se provi a compilare Y.py dalla riga di comando. Durante la compilazione di Y.py, il compilatore preme l'istruzione import prima che definisca Y2. Quindi inizia a compilare X.py. Presto colpisce l'istruzione import in X.py che richiede Y2. Ma Y2 non è definito, quindi la compilazione non riesce.
Nota che se modifichi X.py per importare Y1, la compilazione avrà sempre successo, indipendentemente dal file che compili. Tuttavia, se modifichi il file Y.py per importare il simbolo X2, nessuno dei due file verrà compilato.
Ogni volta che il modulo X, o qualsiasi modulo importato da X potrebbe importare il modulo corrente, NON utilizzare:
from X import Y
Ogni volta che pensi che ci possa essere un'importazione circolare, dovresti anche evitare i riferimenti in fase di compilazione a variabili in altri moduli. Considera il codice dall'aspetto innocente:
import X
z = X.Y
Supponiamo che il modulo X importi questo modulo prima che questo modulo importi X. Supponiamo inoltre che Y sia definito in X dopo l'istruzione import. Quindi Y non verrà definito quando questo modulo viene importato e verrà visualizzato un errore di compilazione. Se questo modulo importa prima Y, puoi farla franca. Ma quando uno dei tuoi colleghi cambia innocentemente l'ordine delle definizioni in un terzo modulo, il codice si interrompe.
In alcuni casi è possibile risolvere le dipendenze circolari spostando un'istruzione di importazione in basso sotto le definizioni dei simboli richieste da altri moduli. Negli esempi precedenti, le definizioni prima dell'istruzione import non falliscono mai. Le definizioni dopo l'istruzione import a volte falliscono, a seconda dell'ordine di compilazione. Puoi persino inserire istruzioni di importazione alla fine di un file, a condizione che nessuno dei simboli importati sia necessario in fase di compilazione.
Nota che spostare le istruzioni di importazione verso il basso in un modulo oscura ciò che stai facendo. Compensa questo con un commento nella parte superiore del tuo modulo simile al seguente:
#import X (actual import moved down to avoid circular dependency)
In generale questa è una cattiva pratica, ma a volte è difficile da evitare.