Que se passera-t-il si deux modules s’importent?
Pour généraliser le problème, qu'en est-il des importations cycliques en Python?
Que se passera-t-il si deux modules s’importent?
Pour généraliser le problème, qu'en est-il des importations cycliques en Python?
Réponses:
L' an dernier, il y a eu une très bonne discussion à ce sujet sur comp.lang.python . Cela répond assez bien à votre question.
Les importations sont vraiment très simples. N'oubliez pas ce qui suit:
'import' et 'from xxx import yyy' sont des instructions exécutables. Ils s'exécutent lorsque le programme en cours d'exécution atteint cette ligne.
Si un module n'est 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 renvoie 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 renvoie simplement ce module, qu'il soit terminé ou non. C'est la raison pour laquelle les importations cycliques peuvent renvoyer des modules qui semblent en partie vides.
Enfin, le script 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 rapport avec __main__.
Prenez tout cela ensemble et vous ne devriez pas avoir de surprises lors de l'importation de modules.
Si vous faites à l' import foo
intérieur bar
et à l' import bar
intérieur foo
, cela fonctionnera bien. Au moment où quelque chose s'exécute réellement, les deux modules seront entièrement chargés et auront des références l'un à l'autre.
Le problème est quand vous le 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é.
from foo import *
et from bar import *
sera également très bien.
from x import y
, mais obtient toujours l'erreur d'importation circulaire
import
exécution de l' instruction. Il n'y aura donc pas d'erreur, mais vous n'obtiendrez peut-être pas toutes les variables que vous attendez.
from foo import *
et from bar import *
, tout exécuté dans le foo
est dans la phase d'initialisation de bar
, et les fonctions réelles dans bar
n'ont pas encore été définies ...
Les importations cycliques se terminent, mais vous devez faire attention à ne pas utiliser les modules importés cycliquement 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 les éléments suivants:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out
Lors de la seconde importation de b.py (dans la seconde a in
), l'interpréteur Python ne réimportera b
pas, car il existe déjà dans le module dict.
Si vous essayez d'accéder à b.x
partir de a
lors de l'initialisation du module, vous obtiendrez un AttributeError
.
Ajoutez la ligne suivante à a.py
:
print b.x
Ensuite, la sortie 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'
En effet, les modules sont exécutés lors de l'importation et au moment de l' b.x
accès, la ligne x = 3
n'a pas encore été exécutée, ce qui ne se produira qu'après b out
.
__name__
place de 'a'
. Au début, je ne savais vraiment pas pourquoi un fichier serait exécuté deux fois.
Comme d'autres réponses 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 import lorsque le fichier est importé par d'autres modules. Seulement s'il existe une dépendance circulaire logique, cela échouera.
La plupart des importations circulaires ne sont pas en fait des importations circulaires logiques, mais génèrent plutôt des ImportError
erreurs, en raison de la façon dont import()
les instructions de niveau supérieur du fichier entier sont appelées.
Ceux-ci ImportErrors
peuvent presque toujours être évités si vous voulez que vos importations soient au top :
Considérez 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 David Beazleys excellent talk Modules et packages: Live and Let Die! - PyCon 2015 , 1:54:00
voici une façon 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 s'il ImportError
est levé, car il est déjà importé, il le tirera du cache d'importation.
PS: Vous devez lire l'intégralité de cet article dans la voix de David Beazley.
J'ai ici un exemple 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
import bar
à foo.py
la fin
bar
et les foo
deux doivent utiliser gX
, la solution la plus «propre» consiste à mettre gX
dans un autre module et à avoir les deux foo
et à bar
importer ce module. (le plus propre dans le sens où il n'y a pas de dépendances sémantiques cachées.)
bar
ne trouve même pas gX
dans le foo. l'importation circulaire est bonne en soi, mais c'est juste que ce gX
n'est pas défini quand il est importé.
Module a.py:
import b
print("This is from module a")
Module b.py
import a
print("This is from module b")
L'exécution du "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 lors de l'exécution du "Module a" est répertorié ici:
import b
. il visitera donc le module bimport a
. il visitera donc le module aimport b
mais notez que cette ligne ne sera plus exécutée , car chaque fichier en python exécute une ligne d'importation juste pour une fois, peu importe où ou quand elle est exécutée. il passera donc à la ligne suivante et s'imprimera "This is from module a"
."This is from module b"
"This is from module a"
et le programme sera terminé.Je suis entièrement d'accord avec la réponse de pythoneer ici. Mais je suis tombé sur un code qui était imparfait avec les importations circulaires et a causé des problèmes lors de l'ajout de tests unitaires. Donc, pour le corriger rapidement 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 souhaite corriger une erreur d'importation sans trop modifier le code.
À votre santé!
Il y a beaucoup de bonnes réponses ici. Bien qu'il existe généralement des solutions rapides au problème, dont certaines semblent plus pythoniques que d'autres, si vous avez le luxe de refactoriser, une autre approche consiste à analyser l'organisation de votre code et à supprimer la dépendance circulaire. Vous pouvez par exemple constater que vous avez:
Fichier a.py
from b import B
class A:
@staticmethod
def save_result(result):
print('save the result')
@staticmethod
def do_something_a_ish(param):
A.save_result(A.use_param_like_a_would(param))
@staticmethod
def do_something_related_to_b(param):
B.do_something_b_ish(param)
Fichier b.py
from a import A
class B:
@staticmethod
def do_something_b_ish(param):
A.save_result(B.use_param_like_b_would(param))
Dans ce cas, il suffit de déplacer une méthode statique vers un fichier distinct, par exemple c.py
:
Fichier c.py
def save_result(result):
print('save the result')
permettra de supprimer la save_result
méthode de A, et donc de supprimer l'importation de A de a dans b:
Fichier refacturé a.py
from b import B
from c import save_result
class A:
@staticmethod
def do_something_a_ish(param):
A.save_result(A.use_param_like_a_would(param))
@staticmethod
def do_something_related_to_b(param):
B.do_something_b_ish(param)
Fichier refacturé b.py
from c import save_result
class B:
@staticmethod
def do_something_b_ish(param):
save_result(B.use_param_like_b_would(param))
En résumé, si vous avez un outil (par exemple pylint ou PyCharm) qui rend compte des méthodes qui peuvent être statiques, il suffit de jeter un staticmethod
décorateur dessus pour ne pas être le meilleur moyen de faire taire l'avertissement. Même si la méthode semble liée à la classe, il pourrait être préférable de la séparer, surtout si vous avez plusieurs modules étroitement liés qui pourraient avoir besoin de la même fonctionnalité et que vous avez l'intention de pratiquer les principes DRY.
Les importations circulaires peuvent prêter à confusion car l'importation signifie deux choses:
Le premier est effectué une seule fois, tandis que le second à chaque déclaration d'importation. L'importation circulaire crée une situation lorsque le module d'importation utilise un module importé avec du 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 montre.
Les importations circulaires ne sont pas le mal ultime à éviter à tout prix. Dans certains frameworks comme Flask, ils sont assez naturels et peaufiner 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 __name__ == '__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, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"
a.py
print 'a in, __name__ = {}'.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, __name__ = b # b code execution started
b imports a
a in, __name__ = 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
J'ai résolu le problème de la manière suivante, et cela fonctionne bien sans aucune erreur. Considérez deux fichiers a.py
et b.py
.
J'ai ajouté cela à a.py
et cela a fonctionné.
if __name__ == "__main__":
main ()
import b
y = 2
def main():
print ("a out")
print (b.x)
if __name__ == "__main__":
main ()
import a
print ("b out")
x = 3 + a.y
La sortie que j'obtiens est
>>> b out
>>> a out
>>> 5
Ok, je pense que j'ai une solution assez cool. Disons que vous avez un fichier a
et un fichier b
. Vous avez un def
ou class
dans le fichier b
que vous souhaitez utiliser dans le module a
, mais vous avez quelque chose d' autre, que ce soit un def
, class
ou une variable de fichier a
que vous avez besoin dans votre définition ou de la classe dans le fichier b
. Ce que vous pouvez faire est, au bas du fichier a
, après avoir appelé la fonction ou la classe dans le fichier a
qui est nécessaire dans le fichier b
, mais avant d'appeler la fonction ou la classe à partir du fichier b
dont vous avez besoin pour le fichier a
, dites import b
ensuite, et voici la partie clé , dans toutes les définitions ou classes du fichier b
qui nécessitent le def
ou à class
partir du fichiera
(appelons-le CLASS
), vous ditesfrom a import CLASS
Cela fonctionne car vous pouvez importer un fichier b
sans que Python n'exécute aucune des instructions d'importation dans le fichier b
, et vous échappez ainsi à toute importation circulaire.
Par exemple:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
class B(object):
def __init__(self, number):
self.number = number
def dostuff(self):
from a import CLASS
print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."
Voila.
from a import CLASS
ne saute pas réellement l'exécution de tout le code dans a.py. Voici ce qui se passe réellement: (1) Tout le code dans a.py est exécuté comme un module spécial "__main__". (2) À import b
, le code de niveau supérieur dans b.py est exécuté (définissant la classe B), puis le contrôle retourne à "__main__". (3) "__main__" passe éventuellement le contrôle à go.dostuff()
. (4) lorsque doStuff () vient import a
, il exécute tout le code dans a.py à nouveau , cette fois que le module « a »; puis il importe l'objet CLASS du nouveau module "a". Donc, en fait, cela fonctionnerait aussi bien si vous l' import a
utilisiez n'importe où dans b.py.