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?

265
demandé sur NullUserException 2009-04-13 20:07:07

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.

233
répondu Shane C. Mason 2012-06-15 15:39:32

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é.

217
répondu user2357112 2016-05-09 18:35:39

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 .

91
répondu Torsten Marek 2016-05-27 17:44:44

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.

23
répondu Sebastian Wozny 2015-11-05 14:51:51

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
8
répondu Xolve 2014-09-06 20:46:06

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é!

3
répondu radtek 2017-03-02 19:11:28

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:

  1. la première ligne est import b . il visitera donc le module B
  2. la première ligne du module b est import a . donc, il va visiter un module
  3. 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" .
  4. 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"
  5. Les lignes du Module B
  6. sont entièrement exécutées. nous reviendrons donc au module a où nous avons commencé le module B.
  7. La ligne
  8. 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é.
1
répondu Mohsen Haddadi 2018-07-06 20:58:05

Circulaire importations peut être source de confusion en raison de l'importation fait deux choses:

  1. il exécute le code de module importé
  2. 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
0
répondu Jacek Błocki 2018-06-30 09:57:33

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é.

-2
répondu Cary Shindell 2015-03-31 12:04:01