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?
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
.
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 deworld
, leworld
script est exécuté. - Le
world
le script importeField
, ce qui entraîne l'exécution du scriptentities.field
. - ce processus se poursuit jusqu'à ce que vous atteigniez le script
entities.post
car vous avez essayé d'importerPost
- le script
entities.post
provoque l'exécution du modulephysics
car il tente d'importerPostBody
- Enfin,
physics
essaie d'importerPost
à partir deentities.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 membrePost
car il n'a pas fini de s'Exécuter pour définirPost
- 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.
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.
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...."
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.