Importations circulaires (ou 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?
9 réponses
il y a eu une très bonne discussion à ce sujet à comp.lang.python l'année dernière. Ça répond assez bien à ta question.
importations sont assez simple vraiment. Rappelez-vous juste ce qui suit:
"importer" et "de xxx importation yyy" sont des instructions exécutables. Ils exécutent quand le programme atteint cette ligne.
si un module n'est pas dans le SAJR.module, puis un import crée le nouveau module l'entrée dans le sys.module puis exécute le code dans le module. Il n'a pas renvoie le contrôle au module d'appel jusqu'à la fin de l'exécution.
si un module existe dans sys.modules puis une importation renvoie simplement que module qu'il ait terminé ou non l'exécution. C'est la raison pourquoi les importations cycliques peuvent renvoyer des modules qui semblent partiellement vides.
enfin, le script d'exécution fonctionne dans un module nommé _ _ main__, importer le script sous son propre nom va créer un nouveau module sans rapport avec __principal.__
prenez ce lot ensemble et vous ne devriez pas avoir de surprises lors de l'importation module.
si vous faites import foo
à l'intérieur de bar
et import bar
à l'intérieur de foo
, ça marchera très bien. D'ici à ce que quelque chose tourne, les deux modules seront complètement chargés et auront des références l'un à l'autre.
le problème est quand à 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é (de sorte que le nom que nous importons existe) avant qu'il puisse être importé.
les importations cycliques prennent fin, mais vous devez faire attention à ne pas utiliser les modules importés cycliquement lors de l'initialisation des modules.
Envisager 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 un.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
sur la deuxième importation de B. py( dans le second a in
), le Python interpreter n'importe pas b
de nouveau, car il existe déjà dans le module dict.
si vous essayez d'accéder à b.x
de a
pendant l'initialisation du module, vous obtiendrez un AttributeError
.
ajouter la ligne suivante à a.py
:
print b.x
alors, 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'
C'est parce que les modules sont exécutés sur l'importation et à l'époque b.x
est accessible, la ligne x = 3
n'a pas encore été exécutée, ce qui n'arrivera qu'après b out
.
comme d'autres réponses décrivent ce modèle est acceptable en python:
def dostuff(self):
from foo import bar
...
qui évitera l'exécution de la déclaration d'importation lorsque le fichier est importé par d'autres modules. Ce n'est que s'il y a une dépendance circulaire logique, cela échouera.
la plupart des importations circulaires ne sont pas réellement des importations circulaires logiques, mais plutôt augmenter ImportError
erreurs, en raison de la façon import()
évalue des déclarations de haut niveau de l'ensemble fichier lorsqu'il est appelé.
ces ImportErrors
peuvent presque toujours être évités si vous voulez vos importations sur le dessus :
considérez cette importation circulaire:
App
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
"1519220920 Application" B
# 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 paquets: Live and Let Die! - PyCon 2015 , 1:54:00
, voici une façon de traiter avec la circulaire importations en python:
try:
from images.serializers import SimplifiedImageSerializer
except ImportError:
import sys
SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']
cela tente d'importer SimplifiedImageSerializer
et si ImportError
est soulevé, parce qu'il est déjà importé, il tirera de l'importcache.
PS: vous devez lire ce post entier 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"
en 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 entièrement 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 a causé des problèmes en essayant d'ajouter des tests unitaires. Ainsi, pour le corriger rapidement sans changer tout ce que vous pouvez résoudre le problème en faisant 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 changer le code.
santé!
Module A. py:
import b
print("This is from module a")
Module B. py
import a
print("This is from module b")
exécution" Module A "sortie:
>>>
'This is from module a'
'This is from module b'
'This is from module a'
>>>
il sort ces 3 lignes alors qu'il était censé sortir infinitif en raison de l'importation circulaire. Ce qui se passe ligne par ligne pendant l'exécution du"Module a" est indiqué ici:
- la première ligne est
import b
. il visitera donc le module B - la première ligne du module b est
import a
. donc, il va visiter un module - la première ligne du module a est
import b
mais notez que cette ligne ne sera plus exécutée à nouveau , 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 imprimera"This is from module a"
. - après avoir visité tout le module a du module b, Nous sommes toujours au module B. ainsi, la ligne suivante va imprimer
"This is from module b"
Les lignes du Module B - sont entièrement exécutées. nous reviendrons donc au module a où nous avons commencé le module B. La ligne
- import b a déjà été exécutée et ne le sera plus. la ligne suivante affichera
"This is from module a"
et le programme sera terminé.
Circulaire importations peut être source de confusion en raison de l'importation fait deux choses:
- il exécute le code de module importé
- ajoute le module importé à la table de symboles globale du module d'importation
le premier n'est fait qu'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 d'objets créé après déclaration d'importation. L'échantillon de code ci-dessous le démontre.
Circulaire importations ne sont pas le mal ultime, à éviter à tout prix. Dans certains cadres comme Flask ils sont tout à fait naturel et peaufiner votre code pour les éliminer ne rend pas le code 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. 151950920"
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"
python main.py résultats 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
Ok, je pense que j'ai une solution assez cool.
Disons que vous avez les fichiers a
et b
.
Vous avez un def
ou un class
dans le fichier b
que vous voulez utiliser dans le module a
, mais vous avez autre chose, soit un def
, class
, ou une variable du fichier a
dont vous avez besoin dans votre définition ou 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 fichier a
qui est nécessaire dans le fichier b
, mais avant d'appeler la fonction ou la classe du fichier b
que vous avez besoin pour le fichier a
, dites import b
Puis, Et voici la partie clé , dans toutes les définitions ou classes du fichier b
qui ont besoin du def
ou class
du fichier a
(appelons-le CLASS
), vous dites from a import CLASS
cela fonctionne parce que vous pouvez importer le fichier b
sans que Python n'exécute aucune des déclarations d'importation dans le fichier b
, et donc vous échappez à toute importation circulaire.
par exemple:
Fichier un:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
"1519340920 Fichier" b:
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."
le tour est joué.