Importation circulaire Python?

Donc, je reçois 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 cela fonctionne? Existe-t-il une règle non écrite sur l'importation circulaire? Comment puis-je utiliser la même classe plus bas dans la pile d'appel?

67
demandé sur PersianGulf 2014-03-05 06:26:05

5 réponses

Je pense que la réponse actuellement acceptée par jpmc26 est trop lourde sur les importations circulaires. Ils peuvent fonctionner très bien, si vous les configurez correctement.

Le moyen le plus simple de le faire est d'utiliser la syntaxe import my_module, plutôt que from my_module import some_object. Le premier fonctionnera presque toujours, même si my_module inclus nous importe en arrière. Ce dernier ne fonctionne que si my_object est déjà défini dans my_module, ce qui peut ne pas être le cas dans une importation circulaire.

Être spécifique À votre cas: Essayez de changer de entities/post.py faire import physics et ensuite, reportez-vous à physics.PostBody plutôt qu'à PostBody directement. De même, changez physics.py pour faire import post et ensuite utilisez post.Post plutôt que simplement Post.

102
répondu Blckknght 2014-03-05 22:30:30

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 que le corps d'une fonction. Un import est juste une commande comme n'importe quel autre (affectation, un appel de fonction, def, class). En supposant que vos importations se produisent en haut du script, alors voici ce qui se passe:

  • Lorsque vous essayez d'importer World à partir de world, le world script est exécuté.
  • Le world le script importe Field, ce qui entraîne l'exécution du script entities.field.
  • ce processus se poursuit jusqu'à ce que vous atteigniez le script entities.post car vous avez essayé d'importer Post
  • le script entities.post provoque l'exécution du module physics car il tente d'importer PostBody
  • Enfin, physics essaie d'importer Post à partir de entities.post
  • Je ne suis pas sûr si le module entities.post 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 membre Post car il n'a pas fini de s'Exécuter pour définir Post
  • de toute façon, une erreur se produit parce que Post n'est pas là pour être importé

Donc, non, ce n'est pas "travailler plus haut dans la pile d'appel". C'est une trace de pile de l'endroit où l'erreur s'est produite, ce qui signifie qu'il a commis une erreur en essayant d'importer Post dans cette classe. Vous ne devriez pas utiliser d'importations circulaires. Au mieux, il a un avantage négligeable (typiquement, aucun avantage), et il provoque ce type de problèmes. Il charge tout développeur de le maintenir, les forçant à marcher sur des coquilles d'œufs pour éviter de le casser. Refactorisez votre organisation de module.

43
répondu jpmc26 2017-03-18 23:12:34

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 des appels de méthode, et pour les comprendre, vous devriez y penser comme des appels de méthode.

Lorsque vous effectuez une importation, ce qui se passe dépend si le fichier que vous importez existe déjà dans la table du module. Si c'est le cas, Python utilise tout ce qui est actuellement dans le symbole table. Si ce n'est pas le cas, Python commence à lire le fichier du module, à compiler/exécuter/importer 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 doivent encore être 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 compilez le fichier X.py. le compilateur commence par définir la méthode X1, puis frappe l'instruction import dans X.py. cela provoque l' compilateur pour mettre en pause la compilation X.py et commencer à compiler Y.py. peu de temps après, le compilateur frappe l'instruction import dans Y.py. depuis X.py est déjà dans la table du module, Python utilise l'incomplet existant X.py tableau des symboles pour satisfaire les références demandées. Tous les symboles apparaissant avant l'instruction import dans X.py sont maintenant dans la table des symboles, mais tous les symboles après ne le sont pas. Puisque X1 apparaît maintenant avant l'instruction import, il est importé avec succès. Python reprend ensuite la compilation Y.py. Ce faisant il définit Y2 et termine la compilation Y.py. il reprend ensuite la compilation de X.py, et trouve Y2 dans le Y.py table des symboles. La Compilation finit par se terminer 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 Y.py, le compilateur frappe l'instruction import avant de définir Y2. Ensuite, il commence à compiler X.py. bientôt, il frappe la déclaration d'importation dans X.py cela nécessite Y2. Mais Y2 est indéfini, donc la compilation échouer.

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 fichier ne sera compilé.

Chaque fois que le module X, ou tout module importé par X peut importer le module actuel, ne pas utiliser:

from X import Y

Chaque fois que vous pensez qu'il peut y avoir une importation circulaire, vous devriez également éviter les références à la compilation des variables dans d'autres modules. Considérez l'innocent code de recherche:

import X
z = X.Y

Supposons que le module X importe ce module avant que ce module importe X. supposons En outre que Y soit défini dans X après l'instruction import. Alors Y ne sera pas défini lorsque ce module est importé, et vous obtiendrez une erreur de compilation. Si ce module importe Y en premier, vous pouvez vous en sortir. Mais quand l'un de vos collègues change innocemment l'ordre des définitions dans un troisième module, le code se casse.

Dans certains cas, vous pouvez résoudre les dépendances circulaires en déplacer une instruction d'importation en dessous des 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 import échouent parfois, en fonction de l'ordre de compilation. Vous pouvez même mettre des déclarations d'importation à la fin d'un fichier, tant que aucun des symboles importés sont nécessaires au moment de la compilation.

Notez que déplacer des instructions d'importation dans un module obscurcit ce que vous faites. Compenser 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 il est parfois difficile à éviter.

21
répondu Gene Olson 2016-10-17 22:17:01

Pour ceux d'entre vous qui, comme moi, viennent à ce problème de Django, vous devriez savoir que les docs fournissent 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 Manufacturer 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 genre de la référence peut être utile lors de la résolution des dépendances d'importation circulaires entre deux applications...."

8
répondu Malik A. Rumi 2016-09-26 20:46:45

Si vous rencontrez ce problème dans une application assez complexe, il peut être difficile de refactoriser toutes vos importations. PyCharm offre un quickfix pour cela qui changera automatiquement toute l'utilisation des symboles importés.

entrez la description de l'image ici

1
répondu Andreas Bergström 2018-09-14 10:39:49