84 votes

Dépendance d'importation circulaire en Python

Supposons que j'aie la structure de répertoire suivante :

a\
    __init__.py
    b\
        __init__.py
        c\
            __init__.py
            c_file.py
        d\
            __init__.py
            d_file.py

Dans le cadre de la a du paquet __init__.py , le c est importé. Mais c_file.py importations a.b.d .

Le programme échoue en disant b n'existe pas lorsque c_file.py tente d'importer a.b.d . (Et il n'existe pas vraiment, car nous étions en train de l'importer).

Comment remédier à ce problème ?

1 votes

Peut-être pourriez-vous essayer les importations relatives ? stackoverflow.com/questions/72852/

1 votes

0 votes

Également à titre de référence, il semble que les importations circulaires soient autorisées sous Python 3.5 (et probablement au-delà) mais pas sous 3.4 (et probablement en dessous).

164voto

Dirk Points 17809

Vous pouvez différer l'importation, par exemple dans les cas suivants a/__init__.py :

def my_function():
    from a.b.c import Blah
    return Blah()

c'est-à-dire reporter l'importation jusqu'à ce qu'elle soit vraiment nécessaire. Cependant, j'examinerais également de près les définitions/utilisations de mes paquets, car une dépendance cyclique comme celle qui a été signalée pourrait indiquer un problème de conception.

4 votes

Parfois, les références circulaires sont vraiment inévitables. C'est la seule approche qui fonctionne pour moi dans ces circonstances.

1 votes

Cela n'ajouterait-il pas beaucoup de surcharge à chaque appel de foo ?

6 votes

@Mr_and_Mrs_D - seulement modérément. Python conserve tous les modules importés dans un cache global ( sys.modules ), de sorte qu'une fois qu'un module a été chargé, il ne le sera plus. Le code peut impliquer une recherche de nom à chaque appel à my_function Il en va de même pour le code, qui fait référence aux symboles par le biais de noms qualifiés (par ex, import foo; foo.frobnicate() )

66voto

Lasse V. Karlsen Points 148037

Si a dépend de c et que c dépend de a, ne s'agit-il pas en fait de la même unité ?

Vous devriez vraiment examiner pourquoi vous avez divisé a et c en deux paquets, parce que soit vous avez du code que vous devriez séparer dans un autre paquet (pour qu'ils dépendent tous deux de ce nouveau paquet, mais pas l'un de l'autre), soit vous devriez les fusionner en un seul paquet.

119 votes

Oui, on peut considérer qu'il s'agit du même paquet. Mais si cela se traduit par un fichier extrêmement volumineux, ce n'est pas pratique. Je suis d'accord pour dire que les dépendances circulaires signifient souvent que la conception doit être repensée. Mais il y a des modèles de conception où cela est approprié (et où fusionner les fichiers ensemble résulterait en un fichier énorme), donc je pense qu'il est dogmatique de dire que les paquets doivent être combinés ou que la conception doit être réévaluée.

30voto

zachaysan Points 278

Je me suis posé cette question à plusieurs reprises (généralement en traitant des modèles qui ont besoin de se connaître les uns les autres). La solution la plus simple consiste à importer l'ensemble du module, puis à référencer ce dont vous avez besoin.

Ainsi, au lieu de faire

from models import Student

en un seul, et

from models import Classroom

dans l'autre, il suffit de faire

import models

dans l'un d'entre eux, puis appeler models.Classroom quand vous en avez besoin.

19voto

Dépendances circulaires dues aux indications de type

Avec les indications de type, les possibilités de créer des importations circulaires sont plus nombreuses. Heureusement, il existe une solution utilisant la constante spéciale : typing.TYPE_CHECKING .

L'exemple suivant définit un Vertex et une classe Edge classe. Une arête est définie par deux sommets et un sommet maintient une liste des arêtes adjacentes auxquelles il appartient.

Sans indication de type, pas d'erreur

Fichier : vertex.py

class Vertex:
    def __init__(self, label):
        self.label = label
        self.adjacency_list = []

Fichier : edge.py

class Edge:
    def __init__(self, v1, v2):
        self.v1 = v1
        self.v2 = v2

Type Indications Cause ImportError

ImportError : cannot import name 'Edge' from partially initialized module 'edge' (probably due to a circular import)

Fichier : vertex.py

from typing import List
from edge import Edge

class Vertex:
    def __init__(self, label: str):
        self.label = label
        self.adjacency_list: List[Edge] = []

Fichier : edge.py

from vertex import Vertex

class Edge:
    def __init__(self, v1: Vertex, v2: Vertex):
        self.v1 = v1
        self.v2 = v2

Solution utilisant TYPE_CHECKING

Fichier : vertex.py

from typing import List, TYPE_CHECKING

if TYPE_CHECKING:
    from edge import Edge

class Vertex:
    def __init__(self, label: str):
        self.label = label
        self.adjacency_list: List['Edge'] = []

Fichier : edge.py

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from vertex import Vertex

class Edge:
    def __init__(self, v1: 'Vertex', v2: 'Vertex'):
        self.v1 = v1
        self.v2 = v2

Conseils sur les types de titres cotés et non cotés

Dans les versions de Python antérieures à la 3.10, les types importés conditionnellement doivent être placés entre guillemets, ce qui en fait des "références directes", qui les dissimulent à l'interpréteur en cours d'exécution.

Dans Python 3.7, 3.8 et 3.9, une solution de contournement consiste à utiliser l'importation spéciale suivante.

from __future__ import annotations

Cela permet d'utiliser des indications de type non cotées combinées à des importations conditionnelles.

Python 3.10 (Voir PEP 563 -- Évaluation différée des annotations )

Dans Python 3.10, les annotations de fonctions et de variables ne seront plus évaluées au moment de la définition. Au lieu de cela, une forme de chaîne sera préservée dans le fichier annotations dictionnaire. Les vérificateurs de types statiques ne verront aucune différence de comportement, tandis que les outils utilisant des annotations au moment de l'exécution devront améliorer leur performance. devront effectuer une évaluation différée.

La forme chaîne est obtenue à partir de l'AST lors de l'étape de compilation, ce qui signifie que la forme chaîne peut ne pas préserver le formatage exact de l'AST. formatage exact de la source. Note : si une annotation était déjà une chaîne littérale elle sera toujours enveloppée dans une chaîne de caractères.

0voto

John Gilmer Points 651

Le problème est que lorsqu'on travaille à partir d'un répertoire, par défaut, seuls les paquets qui sont des sous-répertoires sont visibles comme candidats à l'importation, donc vous ne pouvez pas importer a.b.d. Vous pouvez cependant importer b.d. puisque b est un sous-paquet de a.

Si vous souhaitez vraiment importer des produits de la mer dans le monde entier, vous pouvez le faire en ligne. c/__init__.py vous pouvez le faire en changeant le chemin d'accès au système pour qu'il soit un répertoire au-dessus de a et en changeant l'importation dans a/__init__.py à importer a.b.c.

Votre a/__init__.py devrait ressembler à ceci :

import sys
import os
# set sytem path to be directory above so that a can be a 
# package namespace
DIRECTORY_SCRIPT = os.path.dirname(os.path.realpath(__file__)) 
sys.path.insert(0,DIRECTORY_SCRIPT+"/..")
import a.b.c

Une difficulté supplémentaire se présente lorsque vous souhaitez exécuter des modules en c en tant que scripts. Dans ce cas, les paquets a et b n'existent pas. Vous pouvez pirater le fichier __int__.py dans le répertoire c afin de faire pointer le sys.path vers le répertoire de premier niveau, puis d'importer __init__ dans tous les modules à l'intérieur de c pour pouvoir utiliser le chemin complet pour importer a.b.d. Je doute que ce soit une bonne pratique que d'importer __init__.py mais cela a fonctionné dans mes cas d'utilisation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X