Importation circulaire Python?

98

Donc j'obtiens cette erreur

Traceback (most recent call last):
  File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in <module>
    from world import World
  File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in <module>
    from entities.field import Field
  File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in <module>
    from entities.goal import Goal
  File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in <module>
    from entities.post import Post
  File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in <module>
    from physics import PostBody
  File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in <module>
    from entities.post import Post
ImportError: cannot import name Post

et vous pouvez voir que j'utilise la même instruction d'importation plus haut et que cela fonctionne? Existe-t-il une règle non écrite concernant l'importation circulaire? Comment utiliser la même classe plus bas dans la pile d'appels?

CpILL
la source

Réponses:

161

Je pense que la réponse de jpmc26, bien que loin d'être fausse , se résume trop aux importations circulaires. Ils peuvent très bien fonctionner, si vous les configurez correctement.

Le moyen le plus simple de le faire est d'utiliser la import my_modulesyntaxe plutôt que from my_module import some_object. La première fonctionnera presque toujours, même si elle my_modulenous importe en retour. Ce dernier ne fonctionne que s'il my_objectest déjà défini dans my_module, ce qui dans une importation circulaire peut ne pas être le cas.

Pour être spécifique à votre cas: essayez de changer entities/post.pypour faire import physics, puis faites référence à physics.PostBodyplutôt que PostBodydirectement. De même, changez physics.pypour faire import entities.postet ensuite utiliser entities.post.Postplutôt que juste Post.

Blckknght
la source
5
Cette réponse est-elle compatible avec les importations relatives?
Joe
17
Pourquoi cela arrive-t-il?
Juan Pablo Santos
4
Il est faux de dire que la non- fromsyntaxe fonctionnera toujours. Si j'ai class A(object): pass; class C(b.B): passdans le module a et class B(a.A): passdans le module b, l'importation circulaire est toujours un problème et cela ne fonctionnera pas.
CrazyCasta
1
Vous avez raison, toutes les dépendances circulaires dans le code de niveau supérieur des modules (comme les classes de base des déclarations de classe dans votre exemple) seront un problème. C'est le genre de situation où la réponse de jpmc selon laquelle vous devriez refactoriser l'organisation du module est probablement correcte à 100%. Déplacez la classe Bdans le module aou déplacez la classe Cdans le module bafin de pouvoir interrompre le cycle. Il est également intéressant de noter que même si une seule direction du cercle a un code de premier niveau impliqué (par exemple, si la classe Cn'existait pas), vous pourriez obtenir une erreur, en fonction du module importé en premier par un autre code.
Blckknght
2
@TylerCrompton: Je ne suis pas sûr de ce que vous entendez par "l'importation de modules doit être absolue". Les importations relatives circulaires peuvent fonctionner, tant que vous importez des modules, pas leur contenu (par exemple from . import sibling_module, pas from .sibling_module import SomeClass). Il y a plus de subtilité lorsque le __init__.pyfichier d' un package est impliqué dans l'importation circulaire, mais le problème est à la fois rare et probablement un bogue dans l' importimplémentation. Voir le bogue Python 23447 , pour lequel j'ai soumis un correctif (qui, hélas, languit).
Blckknght
51

Lorsque vous importez un module (ou un membre de celui-ci) pour la première fois, le code à l'intérieur du module est exécuté séquentiellement comme tout autre code; par exemple, il n'est pas traité différemment du corps d'une fonction. Un importest juste une commande comme une autre (affectation, un appel de fonction, def, class). En supposant que vos importations se produisent en haut du script, voici ce qui se passe:

  • Lorsque vous essayez d'importer Worlddepuis world, le worldscript est exécuté.
  • Le worldscript est importé Field, ce qui entraîne l' entities.fieldexécution du script.
  • Ce processus se poursuit jusqu'à ce que vous atteigniez le entities.postscript car vous avez essayé d'importerPost
  • Le entities.postscript provoque physicsl'exécution du module car il tente d'importerPostBody
  • Enfin, physicsessaie d'importer Postdepuisentities.post
  • Je ne sais pas si le entities.postmodule existe encore en mémoire, mais cela n'a vraiment pas d'importance. Soit le module n'est pas en mémoire, soit le module n'a pas encore de Postmembre car il n'a pas fini de s'exécuter pour définirPost
  • Quoi qu'il en soit, une erreur se produit car il Postn'est pas là pour être importé

Donc non, ce n'est pas "travailler plus haut dans la pile d'appels". Il s'agit d'une trace de pile de l'endroit où l'erreur s'est produite, ce qui signifie qu'elle a tenté d'importer Postdans cette classe. Vous ne devez pas utiliser d'importations circulaires. Au mieux, il a des avantages négligeables (généralement aucun avantage), et cela cause des problèmes comme celui-ci. Cela pèse sur tout développeur qui l'entretient, les obligeant à marcher sur des coquilles d'œufs pour éviter de les casser. Refactorisez l'organisation de vos modules.

jpmc26
la source
1
Devrait être isinstance(userData, Post). Quoi qu'il en soit, vous n'avez pas le choix. L'import circulaire ne fonctionnera pas. Le fait que vous ayez des importations circulaires est une odeur de code pour moi. Cela suggère que vous avez certaines fonctionnalités qui devraient être déplacées vers un troisième module. Je ne pourrais pas dire quoi sans regarder les deux classes entières.
jpmc26
3
@CpILL Au bout d'un moment, une option très piratée m'est venue. Si vous ne pouvez pas vous déplacer pour le moment (en raison de contraintes de temps ou autre), vous pouvez effectuer votre importation localement dans la méthode où vous l'utilisez. Un corps de fonction à l'intérieur defn'est pas exécuté tant que la fonction n'est pas appelée, de sorte que l'importation ne se produira pas tant que vous n'aurez pas appelé la fonction. D'ici là, le imports devrait fonctionner car l'un des modules aurait été complètement importé avant l'appel. C'est un hack absolument dégoûtant, et il ne devrait pas rester dans votre base de code pendant une période de temps significative.
jpmc26
15
Je pense que votre réponse est trop sévère sur les importations circulaires. Les importations circulaires fonctionnent généralement si vous faites juste import fooplutôt que from foo import Bar. C'est parce que la plupart des modules définissent simplement des éléments (comme des fonctions et des classes) qui s'exécutent plus tard. Les modules qui font des choses importantes lorsque vous les importez (comme un script non protégé par if __name__ == "__main__") peuvent toujours poser problème, mais ce n'est pas trop courant.
Blckknght
6
@Blckknght Je pense que vous vous préparez à passer du temps sur des problèmes étranges sur lesquels d'autres personnes devront enquêter et être confus si vous utilisez des importations circulaires. Ils vous obligent à passer du temps en faisant attention à ne pas trébucher dessus, et en plus de cela, il y a une odeur de code que votre conception doit être refactorisée. Je me suis peut-être trompé sur leur faisabilité technique, mais c'est un choix de conception terrible destiné à causer des problèmes tôt ou tard. La clarté et la simplicité sont le Saint Graal de la programmation et les importations circulaires violent les deux dans mon livre.
jpmc26
6
Alternativement; vous avez trop fractionné vos fonctionnalités et c'est la cause des importations circulaires. Si vous avez deux choses qui dépendent l'une de l'autre tout le temps ; il peut être préférable de les mettre dans un seul fichier. Python n'est pas Java; aucune raison de ne pas regrouper les fonctionnalités / classes dans un seul fichier pour éviter une logique d'importation étrange. :-)
Mark Ribau
40

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.

Gene Olson
la source
2
Je ne pense pas qu'il y ait du tout de compilateur ou de temps de compilation en python
pkqxdd
6
Python ne disposer d' un compilateur, et est compilé @pkqxdd, la compilation est généralement juste caché loin de l'utilisateur. Cela peut être un peu déroutant, mais il serait difficile pour l'auteur de donner cette description admirablement claire de ce qui se passe sans une référence au "temps de compilation" de Python, quelque peu obscurci.
Hank
Je suis allé de l'avant pour essayer ceci sur ma machine et j'ai obtenu un résultat différent. Ran X.py mais erreur "impossible d'importer le nom" Y2 "depuis" Y "". Ran Y.py sans problème cependant. Je suis sur Python 3.7.5 pourriez-vous aider à expliquer quel est le problème ici?
xuefeng huang
18

Pour ceux d'entre vous qui, comme moi, abordez ce problème depuis Django, sachez que la documentation fournit une solution: https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

"... Pour faire référence à des modèles définis dans une autre application, vous pouvez spécifier explicitement un modèle avec l'étiquette d'application complète. Par exemple, si le modèle Fabricant ci-dessus est défini dans une autre application appelée production, vous devez utiliser:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',
        on_delete=models.CASCADE,
)

Ce type de référence peut être utile lors de la résolution des dépendances d'importation circulaire entre deux applications. ... "

Malik A. Rumi
la source
6
Je sais que je ne suis pas censé utiliser un commentaire pour dire "merci", mais cela me tourmente depuis quelques heures. Merci merci merci!!!
MikeyE
Je suis d'accord avec @MikeyE. J'ai lu plusieurs blogs et Stackoverflows essayant de remédier à cela avec PonyORM. Là où d'autres disent que c'est une mauvaise pratique, ou pourquoi coderiez-vous vos classes pour qu'elles soient circulaires, eh bien les ORM sont exactement là où cela se produit. Étant donné que de nombreux exemples mettent tous les modèles dans le même fichier et que nous suivons ces exemples, sauf que nous utilisons un modèle par fichier, le problème n'est pas clair lorsque Python ne parvient pas à se compiler. Pourtant, la réponse est si simple. Comme Mike l'a souligné, merci beaucoup.
trash80
4

J'ai pu importer le module dans la fonction (uniquement) qui nécessiterait les objets de ce module:

def my_func():
    import Foo
    foo_instance = Foo()
Alexander Shubert
la source
comment élégant de python
Yaro
2

Si vous rencontrez ce problème dans une application assez complexe, il peut être fastidieux de refactoriser toutes vos importations. PyCharm propose un correctif rapide pour cela qui modifiera automatiquement toutes les utilisations des symboles importés.

entrez la description de l'image ici

Andreas Bergström
la source
0

J'utilisais ce qui suit:

from module import Foo

foo_instance = Foo()

mais pour circular referencem'en débarrasser, j'ai fait ce qui suit et cela a fonctionné:

import module.foo

foo_instance = foo.Foo()
MKJ
la source