186 votes

Comment éviter les importations circulaires en Python ?

Je sais que la question des importations circulaires en Python a déjà été soulevée à de nombreuses reprises et j'ai lu ces discussions. Le commentaire qui revient souvent dans ces discussions est qu'une importation circulaire est le signe d'une mauvaise conception et que le code devrait ê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 renvoie une instance de la classe.

Plus précisément, 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 d'autres choses. Cette situation est similaire à celle des ensembles et des ensembles frozens ou à celle des listes et des tuples.

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

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. La classe A possède alors une méthode qui prend une instance de la classe B et renvoie une instance de la classe A (en convertissant le tuple en liste) et, de la même manière, la classe B possède une méthode qui prend une instance de la classe A et renvoie une instance de la classe B (en convertissant la liste en tuple).

351voto

Brendan Abel Points 3745

Considérons l'exemple suivant de paquet python où a.py y b.py dépendent les uns des autres :

/package
    __init__.py
    a.py
    b.py

Types de problèmes d'importation circulaire

Les dépendances circulaires à l'importation se répartissent généralement en deux catégories, selon qu'il s'agit de de ce que vous essayez d'importer et de l'endroit où vous l'utilisez dans chaque catégorie. module. (Et si vous utilisez Python 2 ou 3).

1. Erreurs lors de l'importation de modules avec des importations circulaires

Dans certains cas, il suffit de importation un module avec une dépendance d'importation circulaire peut entraîner des erreurs même si vous ne faites référence à rien dans le module importé. importé.

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

import package.a           # (1) Absolute import
import package.a as a_mod  # (2) Absolute import bound to different name
from package import a      # (3) Alternate absolute import
import a                   # (4) Implicit relative import (deprecated, python 2 only)
from . import a            # (5) Explicit relative import

Malheureusement, seules les 1ère et 4ème options fonctionnent réellement lorsque vous dépendances circulaires (les autres options soulèvent toutes des ImportError ou AttributeError ). En général, vous ne devriez pas utiliser la syntaxe 4ème syntaxe, puisqu'elle ne fonctionne qu'en python2 et qu'elle risque de d'entrer en conflit avec d'autres modules tiers. En réalité, seule la première syntaxe est garantie de fonctionner.

EDIT : Le ImportError y AttributeError Les problèmes ne se posent qu'en python 2. Dans Python 3, les mécanismes d'importation ont été réécrits et tous les (à l'exception de la 4) fonctionneront, même avec la commande dépendances circulaires. Bien que les solutions proposées dans cette section puissent aider à remanier les c aux personnes utilisant Python 2.

Importation absolue

Il suffit d'utiliser la première syntaxe d'importation ci-dessus. L'inconvénient de cette méthode est que les noms d'importation peuvent devenir super long pour les gros colis.

En a.py

import package.b

En b.py

import package.a

Reporter l'importation à plus tard

J'ai vu cette méthode utilisée dans de nombreux paquets, mais elle donne toujours l'impression d'avoir été utilisée par un grand nombre de personnes. hacky pour moi, et je n'aime pas le fait que je ne puisse pas regarder en haut d'un module et voir toutes ses dépendances, je dois chercher dans toutes les fonctions.

En a.py

def func():
    from package import b

En b.py

def func():
    from package import a

Placer toutes les importations dans un module central

Cette méthode fonctionne également, mais présente le même problème que la première méthode, à savoir que tous les appels de paquets et de sous-modules sont super long . Il dispose également de deux deux défauts majeurs : il oblige les tous les sous-modules à importer, même si vous n'en utilisez qu'un ou deux, et vous ne pouvez toujours pas consulter l'un ou l'autre des sous-modules et voir rapidement leurs dépendances en haut, vous devez passer au crible les fonctions.

En __init__.py

from . import a
from . import b

En a.py

import package

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

En b.py

import package

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

2. Erreurs lors de l'utilisation d'objets importés avec des dépendances circulaires

Or, si vous pouvez importer une module avec une importation circulaire circulaire, vous ne pourrez pas importer les objets définis dans le module ni faire référence à ce module importé où que ce soit dans le niveau supérieur du module où vous l'importez. Vous pouvez, cependant, utiliser le module importé à l'intérieur les fonctions et les blocs de code qui ne sont pas pas exécutés lors de l'importation.

Par exemple, ceci fonctionnera :

package/a.py

import package.b

def func_a():
    return "a"

package/b.py

import package.a

def func_b():
    # Notice how package.a is only referenced *inside* a function
    # and not the top level of the module.
    return package.a.func_a() + "b"

Mais cela ne fonctionnera pas

package/a.py

import package.b

class A(object):
    pass

package/b.py

import package.a

# package.a is referenced at the top level of the module
class B(package.a.A):
    pass

Vous obtiendrez une exception

AttributeError : le module "package" n'a pas d'attribut "a".

En règle générale, dans la plupart des cas de dépendances circulaires, il est possible de de refactoriser ou de réorganiser le code pour éviter ces erreurs et déplacer les les références aux modules à l'intérieur d'un bloc de code.

160voto

rumpel Points 2506

N'importez que le module, n'importez pas à partir du module :

Considérer a.py :

import b

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

y b.py :

import a

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

Cela fonctionne parfaitement.

8voto

Christian Haintz Points 135

Nous utilisons une combinaison d'importations absolues et de fonctions 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 frais généraux 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__)

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