web-dev-qa-db-fra.com

Python importation circulaire?

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 déclaration d'importation plus loin et que cela fonctionne? Existe-t-il une règle non écrite concernant l'importation de circulaires? Comment utiliser la même classe plus loin dans la pile d'appels?

85
CpILL

Je pense que la réponse de jpmc26, bien que nullement mal, soit trop lourde sur les importations circulaires. Ils peuvent fonctionner correctement si vous les configurez correctement.

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

Pour être spécifique à votre cas: Essayez de changer entities/post.py faire import physics puis référez-vous à physics.PostBody plutôt que simplement PostBody directement. De même, changez physics.py faire import entities.post puis utilisez entities.post.Post plutôt que simplement Post.

126
Blckknght

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é de manière séquentielle, comme tout autre code. Par exemple, il n’est pas traité différemment du corps d’une fonction. Un import est simplement une commande comme une autre (affectation, appel de fonction, def, class). En supposant que vos importations se trouvent en haut du script, voici ce qui se passe:

  • Lorsque vous essayez d'importer World à partir de world, le script world est exécuté.
  • Le script world importe Field, ce qui provoque le entities.field script à exécuter.
  • Ce processus continue jusqu'à ce que vous atteigniez le entities.post script parce que vous avez essayé d'importer Post
  • Le entities.post script provoque l'exécution du module physics car il tente d'importer PostBody
  • Enfin, physics essaie d'importer Post de entities.post
  • Je ne suis pas sûr que le entities.post Le module existe encore en mémoire, mais cela n'a pas d'importance. Soit le module n'est pas en mémoire, soit n'a pas encore de membre Post car il n'a pas fini de s'exécuter pour définir Post
  • Dans les deux cas, une erreur se produit car Post n'est pas là pour être importé.

Donc non, ce n'est pas "travailler plus haut dans la pile d'appels". Ceci est une trace de la pile de l'endroit où l'erreur s'est produite, ce qui signifie qu'elle a essayé de importer Post dans cette classe. Vous ne devriez pas utiliser les importations circulaires. Au mieux, il présente un avantage négligeable (généralement non avantage) et pose de tels problèmes. Il incombe à tout développeur de l’entretenir, de le forcer à marcher sur des coquilles d’œufs pour éviter de le casser. Refactorisez votre organisation de module.

47
jpmc26

Pour comprendre les dépendances circulaires, vous devez vous rappeler que Python est essentiellement un langage de script. L'exécution des instructions en dehors des méthodes a lieu au moment de la compilation. Les instructions d'importation sont exécutées comme des appels de méthode. devrait penser à eux comme des appels de méthode.

Lorsque vous effectuez une importation, cela dépend si le fichier que vous importez existe déjà dans la table de module. Si tel est le cas, Python utilise tout ce qui se trouve actuellement dans la table des symboles. Sinon, Python commence à lire le fichier de module, à compiler/à exécuter/importer tout ce qu'il 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 sources:

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 valide l'instruction d'importation dans X.py. Cela force le compilateur à suspendre la compilation de X.py et à commencer à compiler Y.py. Peu de temps après, le compilateur atteint l'instruction d'importation dans Y.py. Puisque X.py est déjà dans la table du module, Python utilise la table des symboles X.py incomplète existante pour satisfaire 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 suivants ne le sont pas. Comme X1 apparaît maintenant avant l'instruction d'importation, celle-ci a été importée avec succès. Python reprend alors la compilation de Y.py. Ce faisant, il définit Y2 et se termine. compiler Y.py.Elle reprend ensuite la compilation de X.py et trouve Y2 dans la table des symboles Y.py. La compilation 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 frappe la déclaration d'importation dans X.py qui nécessite Y2. Mais Y2 n'est pas défini et 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 fichier ne sera compilé.

À tout moment où le module X, ou n'importe quel module importé par X, peut importer le module actuel, n'utilisez PAS:

from X import Y

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

import X
z = X.Y

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

Dans certains cas, vous pouvez résoudre les dépendances circulaires en déplaçant une instruction d'importation en dessous des définitions de symbole requises par d'autres modules. Dans les exemples ci-dessus, les définitions précédant l'instruction d'importation n'échouent jamais. Les définitions après l'instruction d'importation échouent parfois, en fonction de 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 lors de la compilation.

Notez que le déplacement des instructions d'importation dans un module masque ce que vous faites. Compensez cela avec un commentaire en haut de votre module, du type suivant:

#import X   (actual import moved down to avoid circular dependency)

En général, c'est une mauvaise pratique, mais il est parfois difficile de l'éviter.

30
Gene Olson

Pour ceux d'entre vous qui, comme moi, consultent ce numéro de Django, sachez que la documentation fournit une solution: https://docs.djangoproject.com/fr/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 de l'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 de dépendances d'importation circulaires entre deux applications. ... "

12
Malik A. Rumi

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

enter image description here

2
Andreas Bergström

J'utilisais ce qui suit:

from module import Foo

foo_instance = Foo()

mais pour se débarrasser de circular reference J'ai fait ce qui suit et cela a fonctionné:

import module.foo

foo_instance = foo.Foo()
0
MKJ