Pour comprendre les dépendances circulaires, vous devez vous rappeler que Python est essentiellement un langage de script. L'exécution d'instructions en dehors des méthodes se produit au moment de la compilation. Les instructions d'importation sont exécutées comme les appels de méthode, et pour les comprendre, vous devez les considérer comme des appels de méthode.
Lorsque vous effectuez une importation, ce qui se passe dépend du fait que le fichier que vous importez existe déjà dans la table des modules. Si c'est le cas, Python utilise ce qui se trouve actuellement dans la table des symboles. Sinon, Python commence à lire le fichier du module, compilant / exécutant / important tout ce qu'il y trouve. Les symboles référencés au moment de la compilation sont trouvés ou non, selon qu'ils ont été vus ou ne sont pas encore vus par le compilateur.
Imaginez que vous avez deux fichiers source:
Fichier X.py
def X1:
return "x1"
from Y import Y2
def X2:
return "x2"
Fichier Y.py
def Y1:
return "y1"
from X import X1
def Y2:
return "y2"
Supposons maintenant que vous compiliez le fichier X.py. Le compilateur commence par définir la méthode X1, puis accède à l'instruction d'importation dans X.py. Cela oblige le compilateur à suspendre la compilation de X.py et à commencer la compilation de Y.py. Peu de temps après, le compilateur accède à l'instruction d'importation dans Y.py. Puisque X.py est déjà dans la table des modules, Python utilise la table des symboles X.py incomplète existante pour satisfaire toutes les références demandées. Tous les symboles apparaissant avant l'instruction d'importation dans X.py sont maintenant dans la table des symboles, mais les symboles après ne le sont pas. Puisque X1 apparaît maintenant avant l'instruction d'importation, il est importé avec succès. Python reprend ensuite la compilation de Y.py. Ce faisant, il définit Y2 et termine la compilation de Y.py. Il reprend ensuite la compilation de X.py et trouve Y2 dans la table des symboles Y.py. La compilation se termine finalement sans erreur.
Quelque chose de très différent se produit si vous essayez de compiler Y.py à partir de la ligne de commande. Lors de la compilation de Y.py, le compilateur accède à l'instruction d'importation avant de définir Y2. Ensuite, il commence à compiler X.py. Bientôt, il atteint l'instruction d'importation dans X.py qui nécessite Y2. Mais Y2 n'est pas défini, donc la compilation échoue.
Veuillez noter que si vous modifiez X.py pour importer Y1, la compilation réussira toujours, quel que soit le fichier que vous compilez. Cependant, si vous modifiez le fichier Y.py pour importer le symbole X2, aucun des fichiers ne sera compilé.
À tout moment où le module X, ou tout module importé par X peut importer le module actuel, n'utilisez PAS:
from X import Y
Chaque fois que vous pensez qu'il peut y avoir une importation circulaire, vous devez également éviter les références de compilation à des variables dans d'autres modules. Considérez le code à l'air innocent:
import X
z = X.Y
Supposons que le module X importe ce module avant que ce module importe X. Supposons en outre que Y est défini dans X après l'instruction d'importation. Ensuite, Y ne sera pas défini lors de l'importation de ce module et vous obtiendrez une erreur de compilation. Si ce module importe d'abord Y, vous pouvez vous en tirer. Mais lorsqu'un de vos collègues change innocemment l'ordre des définitions dans un troisième module, le code se cassera.
Dans certains cas, vous pouvez résoudre les dépendances circulaires en déplaçant une instruction d'importation vers le bas sous les définitions de symboles requises par d'autres modules. Dans les exemples ci-dessus, les définitions avant l'instruction import n'échouent jamais. Les définitions après l'instruction d'importation échouent parfois, selon l'ordre de compilation. Vous pouvez même placer des instructions d'importation à la fin d'un fichier, à condition qu'aucun des symboles importés ne soit nécessaire au moment de la compilation.
Notez que déplacer les instructions d'importation vers le bas dans un module obscurcit ce que vous faites. Compensez cela avec un commentaire en haut de votre module, quelque chose comme ce qui suit:
#import X (actual import moved down to avoid circular dependency)
En général, c'est une mauvaise pratique, mais parfois difficile à éviter.