web-dev-qa-db-fra.com

Dépendance à l'importation circulaire en Python

Disons que j'ai 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 __init__.py du package a, le package c est importé. Mais c_file.py importe a.b.d.

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

Comment résoudre ce problème?

65
Ram Rachum

Si a dépend de c et c dépend de a, ne sont-ils pas la même unité alors?

Vous devriez vraiment examiner pourquoi vous avez scindé a et c en deux packages, soit parce que vous avez du code que vous devez scinder en un autre package (pour que les deux dépendent de ce nouveau package, mais pas l'un de l'autre), ou vous devez les fusionner. en un seul paquet.

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

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

c'est-à-dire, différer l'importation jusqu'à ce que cela soit vraiment nécessaire. Cependant, j'examinerais également de près mes définitions/utilisations de paquetages, car une dépendance cyclique comme celle qui est indiquée pourrait indiquer un problème de conception.

147
Dirk

Je me suis posé cette question plusieurs fois (généralement lorsqu'il s'agit de modèles qui doivent se connaître). La solution simple consiste à importer l'ensemble du module, puis à référencer ce dont vous avez besoin.

Donc au lieu de faire 

from models import Student

dans un, et 

from models import Classroom

dans l'autre, il suffit de faire

import models

dans l'un d'eux, appelez ensuite models.Classroom quand vous en avez besoin.

24
zachaysan

Le problème est que lors de l'exécution à partir d'un répertoire, par défaut, seuls les packages qui sont des sous-répertoires sont visibles en tant qu'importations candidates. Vous ne pouvez donc pas importer a.b.d. Vous pouvez cependant importer b.d. puisque b est un sous-paquet de a.

Si vous voulez vraiment importer a.b.d dans c/__init__.py, vous pouvez le faire en modifiant le chemin système en un répertoire au-dessus de a et en modifiant l'importation dans a/__init__.py en importation 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 pose lorsque vous souhaitez exécuter des modules en c en tant que scripts. Ici les paquets a et b n'existent pas. Vous pouvez pirater le __int__.py dans le répertoire c pour qu'il pointe vers sys.path vers le répertoire de niveau supérieur, puis importer __init__ dans n’importe quel module de c pour pouvoir utiliser le chemin complet d’importation a.b.d. Je doute que ce soit une bonne pratique d'importer __init__.py mais cela a fonctionné pour mes cas d'utilisation.

0
John Gilmer

Je suggère le modèle suivant. Son utilisation permettra la complétion automatique et le repérage de type pour fonctionner correctement.

cyclic_import_a.py

import playground.cyclic_import_b

class A(object):
    def __init__(self):
        pass

    def print_a(self):
        print('a')

if __== '__main__':
    a = A()
    a.print_a()

    b = playground.cyclic_import_b.B(a)
    b.print_b()

cyclic_import_b.py

import playground.cyclic_import_a

class B(object):
    def __init__(self, a):
        self.a: playground.cyclic_import_a.A = a

    def print_b(self):
        print('b1-----------------')
        self.a.print_a()
        print('b2-----------------')

Vous ne pouvez pas importer les classes A et B en utilisant cette syntaxe

from playgroud.cyclic_import_a import A
from playground.cyclic_import_b import B

Vous ne pouvez pas déclarer le type de paramètre a dans la méthode B de classe B __ init __, mais vous pouvez le "transtyper" de cette façon:

def __init__(self, a):
    self.a: playground.cyclic_import_a.A = a
0
RaamEE