Que se passera-t-il si deux modules s'importent l'un l'autre?
Pour généraliser le problème, qu'en est-il des importations cycliques en Python?
Il y a eu une très bonne discussion à ce sujet à comp.lang.python l'année dernière. Cela répond assez bien à votre question.
Les importations sont vraiment simples. Rappelez-vous juste ce qui suit:
'import' et 'from xxx import yyy' sont des instructions exécutables. Ils s'exécutent lorsque le programme en cours atteint cette ligne.
Si un module ne se trouve pas dans sys.modules, une importation crée la nouvelle entrée de module dans sys.modules puis exécute le code dans le module. Il ne rend pas le contrôle au module appelant tant que l'exécution n'est pas terminée.
Si un module existe dans sys.modules, une importation retourne simplement ce module, que son exécution soit terminée ou non. C'est la raison pour laquelle les importations cycliques peuvent renvoyer des modules qui semblent partiellement vides.
Enfin, le script en cours d'exécution s'exécute dans un module nommé __main__. L'importation du script sous son propre nom créera un nouveau module sans lien avec __main__.
Prenez cela ensemble et vous ne devriez pas avoir de surprises lors de l'importation de modules.
Si vous faites import foo
à l'intérieur bar
et import bar
à l'intérieur foo
, cela fonctionnera correctement. Au moment où quelque chose fonctionne réellement, les deux modules seront complètement chargés et auront des références les uns aux autres.
Le problème est qu’à la place vous faites from foo import abc
et from bar import xyz
. Parce que maintenant chaque module nécessite que l'autre module soit déjà importé (pour que le nom que nous importons existe) avant de pouvoir être importé.
Les importations cycliques se terminent, mais vous devez veiller à ne pas utiliser les modules importés de manière cyclique lors de l'initialisation du module.
Considérez les fichiers suivants:
a.py:
print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"
b.py:
print "b in"
import a
print "b out"
x = 3
Si vous exécutez a.py, vous obtiendrez ce qui suit:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out
Lors de la deuxième importation de b.py (dans la deuxième a in
), l'interpréteur Python n'importe pas à nouveau b
, car il existe déjà dans le module dict.
Si vous essayez d'accéder à b.x
à partir de a
pendant l'initialisation du module, vous obtiendrez un AttributeError
.
Ajoutez la ligne suivante à a.py
:
print b.x
Ensuite, le résultat est:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
File "a.py", line 4, in <module>
import b
File "/home/shlomme/tmp/x/b.py", line 2, in <module>
import a
File "/home/shlomme/tmp/x/a.py", line 7, in <module>
print b.x
AttributeError: 'module' object has no attribute 'x'
Cela est dû au fait que les modules sont exécutés lors de l'importation et qu'au moment de l'accès à b.x
, la ligne x = 3
n'a pas encore été exécutée, ce qui ne se produira qu'après b out
.
Comme d'autres réponses le décrivent, ce modèle est acceptable en python:
def dostuff(self):
from foo import bar
...
Ce qui évitera l'exécution de l'instruction d'importation lorsque le fichier est importé par d'autres modules. Ce n'est que s'il existe une dépendance circulaire logique que cela échouera.
La plupart des importations circulaires ne sont pas réellement des importations circulaires logiques, mais plutôt des erreurs ImportError
, en raison de la façon dont import()
évalue les instructions de niveau supérieur du fichier entier lors de l'appel.
Ces ImportErrors
peuvent presque toujours être évités si vous voulez que vos importations soient au top :
Considérons cette importation circulaire:
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
# images/serializers.py
from profiles.serializers import SimplifiedProfileSerializer
class SimplifiedImageSerializer(serializers.Serializer):
title = serializers.CharField()
class ImageSerializer(SimplifiedImageSerializer):
profile = SimplifiedProfileSerializer()
De l'excellent discours de David Beazleys Modules et packages: vivre et laisser mourir - PyCon 2015 , 1:54:00
, voici un moyen de traiter les importations circulaires en python:
try:
from images.serializers import SimplifiedImageSerializer
except ImportError:
import sys
SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']
Cela essaie d'importer SimplifiedImageSerializer
et si ImportError
est levé, car il est déjà importé, il sera extrait de la tâche d'importation.
PS: Vous devez lire l'intégralité de ce message à la voix de David Beazley.
J'ai un exemple ici qui m'a frappé!
foo.py
import bar
class gX(object):
g = 10
bar.py
from foo import gX
o = gX()
main.py
import foo
import bar
print "all done"
Sur la ligne de commande: $ python main.py
Traceback (most recent call last):
File "m.py", line 1, in <module>
import foo
File "/home/xolve/foo.py", line 1, in <module>
import bar
File "/home/xolve/bar.py", line 1, in <module>
from foo import gX
ImportError: cannot import name gX
Je suis complètement d'accord avec la réponse de Pythoneer ici. Mais je suis tombé sur un code qui était défectueux avec les importations circulaires et qui posait problème en essayant d'ajouter des tests unitaires. Donc, pour corriger rapidement le problème sans tout changer, vous pouvez résoudre le problème en effectuant une importation dynamique.
# Hack to import something without circular import issue
def load_module(name):
"""Load module using imp.find_module"""
names = name.split(".")
path = None
for name in names:
f, path, info = imp.find_module(name, path)
path = [path]
return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")
Encore une fois, ce n'est pas un correctif permanent, mais peut aider quelqu'un qui veut corriger une erreur d'importation sans trop modifier le code.
À votre santé!
Module a.py:
import b
print("This is from module a")
Module b.py
import a
print("This is from module b")
Exécuter "Module a" produira:
>>>
'This is from module a'
'This is from module b'
'This is from module a'
>>>
Il a sorti ces 3 lignes alors qu'il était supposé sortir infinitival à cause de l'importation circulaire. Ce qui se passe ligne par ligne lorsque vous exécutez "Module a" est répertorié ici:
import b
. donc il visitera le module bimport a
. donc il visitera un moduleimport b
mais remarque que cette ligne ne sera plus exécutée , Parce que chaque fichier dans python exécute une ligne d'importation pour une fois, peu importe où et quand il est exécuté. il passera donc à la ligne suivante et imprimera "This is from module a"
."This is from module b"
"This is from module a"
et le programme sera terminé.J'ai résolu le problème de la manière suivante, et cela fonctionne bien sans erreur. Considérons deux fichiers a.py
et b.py
.
J'ai ajouté ceci à a.py
et cela a fonctionné.
if __== "__main__":
main ()
import b
y = 2
def main():
print ("a out")
print (b.x)
if __== "__main__":
main ()
import a
print ("b out")
x = 3 + a.y
La sortie que je reçois est
>>> b out
>>> a out
>>> 5
Les importations circulaires peuvent prêter à confusion car l'importation a deux effets:
Le premier est effectué une seule fois, tandis que le dernier est indiqué à chaque déclaration d'importation. L'importation circulaire crée une situation lorsque le module d'importation utilise un module importé avec un code partiellement exécuté. En conséquence, il ne verra pas les objets créés après l’instruction d’importation. L'exemple de code ci-dessous le démontre.
Les importations circulaires ne sont pas le mal ultime à éviter à tout prix. Dans certains frameworks tels que Flask, ils sont assez naturels et modifier votre code pour les éliminer ne le rend pas meilleur.
main.py
print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __== '__main__':
print 'imports done'
print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)
b.by
print "b in, __= {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"
a.py
print 'a in, __= {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"
sortie python main.py avec commentaires
import b
b in, __= b # b code execution started
b imports a
a in, __= a # a code execution started
a imports b # b code execution is already in progress
b has x True
b has y False # b defines y after a import,
a out
b out
a in globals() False # import only adds a to main global symbol table
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available