web-dev-qa-db-fra.com

Comment éviter les importations circulaires en Python?

Je sais que la question des importations circulaires dans python a déjà été abordée à maintes reprises et j’ai lu ces discussions. Le commentaire qui a été maintes fois répété est qu’une importation circulaire est le signe d’une mauvaise conception et le code doit être réorganisé pour éviter l'importation circulaire.

Quelqu'un pourrait-il me dire comment éviter une importation circulaire dans cette situation?: J'ai deux classes et je veux que chaque classe ait un constructeur (méthode) qui prend une instance de l'autre classe et retourne une instance de la classe.

Plus spécifiquement, une classe est mutable et une autre est immuable. La classe immuable est nécessaire pour le hachage, la comparaison, etc. La classe mutable est nécessaire pour faire les choses aussi. Ceci est similaire aux ensembles et aux frozensets ou aux listes et aux n-uplets.

Je pourrais mettre les deux définitions de classe dans le même module. Y a-t-il d'autres propositions?

Un exemple de jouet serait la classe A qui a un attribut qui est une liste et la classe B qui a un attribut qui est un tuple. Alors la classe A a une méthode qui prend une instance de la classe B et renvoie une instance de la classe A (en convertissant le tuple en une liste) et de la même manière, la classe B a une méthode qui prend une instance de la classe A et renvoie une instance de la classe B (en convertissant la liste en un tuple).

92
BWW

Seulement importer le module, n'importez pas du module:

Considérer a.py:

import b

class A:
    def bar(self):
        return b.B()

et b.py:

import a

class B:
    def bar(self):
        return a.A()

Cela fonctionne parfaitement bien.

86
rumpel

Prenons l'exemple suivant python où a.py et b.py dépendent les uns des autres:

/package
    __init__.py
    a.py
    b.py

Il y a plusieurs façons d'importer un module en python

import package.a           # Absolute import
import package.a as a_mod  # Absolute import bound to different name
from package import a      # Alternate absolute import
import a                   # Implicit relative import (deprecated, py2 only)
from . import a            # Explicit relative import

Malheureusement, seules les 1re et 4e options fonctionnent réellement lorsque vous avez des dépendances circulaires (toutes les autres soulèvent ImportError ou AttributeError). En général, vous ne devriez pas utiliser la 4ème syntaxe, car elle ne fonctionne que dans python2 et risque de créer des conflits avec d'autres modules tiers. Donc vraiment, seule la première syntaxe est garantie au travail. Cependant, vous avez toujours plusieurs options pour traiter les dépendances circulaires.

EDIT: Les problèmes ImportError et AttributeError ne se produisent que dans python 2. Dans python 3, le mécanisme d'importation a été réécrit et toutes ces instructions d'importation (à l'exception de 4) fonctionneront, même avec des dépendances circulaires.

Utiliser des importations absolues

Utilisez simplement la première syntaxe d'importation ci-dessus. L'inconvénient de cette méthode est que les noms d'importation peuvent être très longs pour les gros paquets.

Dans a.py

import package.b

Dans b.py

import package.a

Reporter l'importation à plus tard

J'ai vu cette méthode utilisée dans de nombreux packages, mais cela me semble toujours hacky, et je n'aime pas trop ne pas pouvoir regarder le haut d'un module et voir toutes ses dépendances, je dois parcourir toutes les fonctions. ainsi que.

Dans a.py

def func():
    from package import b

Dans b.py

def func():
    from package import a

Mettre toutes les importations dans un module central

Cela fonctionne aussi, mais a le même problème que la première méthode, où tous les appels de paquet et de sous-module ont une très longue durée . Il a également deux défauts majeurs - il force tous les sous-modules à importer, même si vous n'en utilisez qu'un ou deux, et vous ne pouvez toujours pas regarder aucun des sous-modules et voir rapidement leurs dépendances au sommet, vous devez passer au crible les fonctions.

Dans __init__.py

from . import a
from . import b

Dans a.py

import package

def func():
    package.b.some_object()

Dans b.py

import package

def func():
    package.a.some_object()

Donc, ce sont vos options (et elles sont toutes un peu nules, IMO). Franchement, cela semble être un bogue flagrant dans la machinerie d'importation python), mais c'est tout simplement mon opinion.

152
Brendan Abel

Nous combinons des importations et des fonctions absolues pour une meilleure lecture et des chaînes d’accès plus courtes.

  • Avantage: chaînes d'accès plus courtes par rapport aux importations absolues pures
  • Inconvénient: un peu plus de temps système en raison d'un appel de fonction supplémentaire

main/sub/a.py

import main.sub.b
b_mod = lambda: main.sub.b

class A():
    def __init__(self):
        print('in class "A":', b_mod().B.__name__)

main/sub/b.py

import main.sub.a
a_mod = lambda: main.sub.a

class B():
    def __init__(self):
        print('in class "B":', a_mod().A.__name__)
6
Christian Haintz