Co się stanie, jeśli dwa moduły zaimportują się nawzajem?
Aby uogólnić problem, co z cyklicznymi importami w Pythonie?
Co się stanie, jeśli dwa moduły zaimportują się nawzajem?
Aby uogólnić problem, co z cyklicznymi importami w Pythonie?
Odpowiedzi:
W zeszłym roku odbyła się naprawdę dobra dyskusja na comp.lang.python . Dość dokładnie odpowiada na twoje pytanie.
Importowanie jest naprawdę bardzo proste. Pamiętaj tylko, że:
„import” i „from xxx import yyy” są instrukcjami wykonywalnymi. Wykonują się, gdy uruchomiony program osiągnie ten wiersz.
Jeśli modułu nie ma w sys.modules, wówczas import tworzy nowy wpis modułu w sys.modules, a następnie wykonuje kod w module. Nie zwraca kontroli do modułu wywołującego, dopóki wykonanie nie zostanie zakończone.
Jeśli moduł istnieje w sys.modules, wówczas import po prostu zwraca ten moduł, niezależnie od tego, czy zakończył wykonywanie. Dlatego cykliczny import może zwracać moduły, które wydają się częściowo puste.
Na koniec skrypt wykonujący działa w module o nazwie __main__, importowanie skryptu pod własną nazwą spowoduje utworzenie nowego modułu niezwiązanego z __main__.
Weź to wszystko razem i nie powinieneś mieć żadnych niespodzianek podczas importowania modułów.
Jeśli zrobisz to import foo
wewnątrz bar
i import bar
wewnątrz foo
, będzie działać dobrze. Do czasu, gdy coś faktycznie się uruchomi, oba moduły zostaną w pełni załadowane i będą miały odniesienia do siebie.
Problem polega na tym, kiedy zamiast tego robisz from foo import abc
i from bar import xyz
. Ponieważ teraz każdy moduł wymaga, aby drugi moduł został już zaimportowany (aby nazwa, którą importujemy, istniała), zanim będzie można ją zaimportować.
from foo import *
i from bar import *
również będzie działać dobrze.
from x import y
, a mimo to nadal pojawia się błąd importu cyklicznego
import
instrukcji. Więc to nie spowoduje błędu, ale możesz nie uzyskać wszystkich oczekiwanych zmiennych.
from foo import *
i from bar import *
wszystko wykonane w foo
jest w fazie inicjalizacji bar
, a rzeczywiste funkcje w bar
nie zostały jeszcze zdefiniowane ...
Cykliczne importowanie zostaje zakończone, ale należy uważać, aby nie używać cyklicznie importowanych modułów podczas inicjalizacji modułu.
Rozważ następujące pliki:
a.py:
print "a in"
import sys
print "b imported: %s" % ("b" in sys.modules, )
import b
print "a out"
b.py:
print "b in"
import a
print "b out"
x = 3
Jeśli wykonasz a.py, otrzymasz:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
b out
a out
Podczas drugiego importu b.py (w drugim a in
) interpreter Pythona nie importuje się b
ponownie, ponieważ już istnieje w module dict.
Jeśli próbujesz uzyskać dostęp b.x
z a
podczas inicjalizacji modułu dostaniesz AttributeError
.
Dodaj następujący wiersz do a.py
:
print b.x
Następnie dane wyjściowe to:
$ python a.py
a in
b imported: False
b in
a in
b imported: True
a out
Traceback (most recent call last):
File "a.py", line 4, in <module>
import b
File "/home/shlomme/tmp/x/b.py", line 2, in <module>
import a
File "/home/shlomme/tmp/x/a.py", line 7, in <module>
print b.x
AttributeError: 'module' object has no attribute 'x'
Wynika to z faktu, że moduły są wykonywane podczas importu i w momencie b.x
dostępu do niego linia x = 3
nie została jeszcze wykonana, co nastąpi dopiero po b out
.
__name__
zamiast 'a'
. Na początku byłem całkowicie zdezorientowany, dlaczego plik zostanie wykonany dwukrotnie.
Jak inne odpowiedzi opisują ten wzorzec jest akceptowalny w pythonie:
def dostuff(self):
from foo import bar
...
Co pozwoli uniknąć wykonywania instrukcji importu, gdy plik zostanie zaimportowany przez inne moduły. Tylko jeśli istnieje logiczna zależność cykliczna, to się nie powiedzie.
Większość importów cyklicznych nie jest w rzeczywistości logicznymi importami cyklicznymi, ale powoduje ImportError
błędy, z powodu sposobu, w jaki import()
wywoływane są instrukcje najwyższego poziomu całego pliku.
Te ImportErrors
mogą być prawie zawsze uniknąć, jeśli chcesz, aby import pozytywnie na górze :
Rozważ ten okrągły import:
# profiles/serializers.py
from images.serializers import SimplifiedImageSerializer
class SimplifiedProfileSerializer(serializers.Serializer):
name = serializers.CharField()
class ProfileSerializer(SimplifiedProfileSerializer):
recent_images = SimplifiedImageSerializer(many=True)
# images/serializers.py
from profiles.serializers import SimplifiedProfileSerializer
class SimplifiedImageSerializer(serializers.Serializer):
title = serializers.CharField()
class ImageSerializer(SimplifiedImageSerializer):
profile = SimplifiedProfileSerializer()
David Beazleys znakomita rozmowa Moduły i pakiety: Live and Let Die! - PyCon 2015 , 1:54:00
oto sposób radzenia sobie z okrągłym importem w pythonie:
try:
from images.serializers import SimplifiedImageSerializer
except ImportError:
import sys
SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']
Spróbuje zaimportować SimplifiedImageSerializer
i jeśli ImportError
zostanie podniesiony, ponieważ jest już zaimportowany, wyciągnie go z importcache.
PS: Musisz przeczytać cały post głosem Davida Beazleya.
Mam tutaj przykład, który mnie uderzył!
foo.py
import bar
class gX(object):
g = 10
bar.py
from foo import gX
o = gX()
main.py
import foo
import bar
print "all done"
W wierszu polecenia: $ python main.py
Traceback (most recent call last):
File "m.py", line 1, in <module>
import foo
File "/home/xolve/foo.py", line 1, in <module>
import bar
File "/home/xolve/bar.py", line 1, in <module>
from foo import gX
ImportError: cannot import name gX
import bar
się foo.py
do końcowego
bar
i foo
oba muszą użyć gX
, „najczystszym” rozwiązaniem jest umieszczenie gX
innego modułu, posiadanie obu foo
i bar
importowanie tego modułu. (najczystszy w tym sensie, że nie ma ukrytych zależności semantycznych).
bar
nie można nawet znaleźć gX
w foo. cykliczny import sam w sobie jest w porządku, ale po prostu gX
nie jest definiowany podczas importowania.
Moduł a.py:
import b
print("This is from module a")
Moduł b.py
import a
print("This is from module b")
Uruchomienie „modułu a” spowoduje:
>>>
'This is from module a'
'This is from module b'
'This is from module a'
>>>
Wyprowadza te 3 wiersze, podczas gdy miał generować nieskończoność z powodu importu cyklicznego. Co dzieje się linia po linii podczas uruchamiania „Modułu a” jest wymienione tutaj:
import b
. więc odwiedzi moduł bimport a
. więc odwiedzi moduł aimport b
ale pamiętaj, że ta linia nie będzie już więcej wykonywana , ponieważ każdy plik w pythonie wykonuje wiersz importu tylko raz, nie ma znaczenia, gdzie i kiedy jest wykonywany. więc przejdzie do następnej linii i wydrukuje "This is from module a"
."This is from module b"
"This is from module a"
i program zostanie zakończony.Całkowicie zgadzam się z odpowiedzią pytającego tutaj. Ale natknąłem się na kod, który był wadliwy przy okrągłym imporcie i powodował problemy podczas próby dodania testów jednostkowych. Aby szybko go załatać bez zmiany wszystkiego, możesz rozwiązać problem, wykonując import dynamiczny.
# Hack to import something without circular import issue
def load_module(name):
"""Load module using imp.find_module"""
names = name.split(".")
path = None
for name in names:
f, path, info = imp.find_module(name, path)
path = [path]
return imp.load_module(name, f, path[0], info)
constants = load_module("app.constants")
Ponownie, nie jest to stała poprawka, ale może pomóc komuś, kto chce naprawić błąd importu bez zmiany zbyt dużej części kodu.
Twoje zdrowie!
Jest tu wiele świetnych odpowiedzi. Chociaż zwykle istnieją szybkie rozwiązania problemu, z których niektóre wydają się bardziej pytoniczne niż inne, jeśli masz luksus polegający na refaktoryzacji, innym podejściem jest analiza organizacji kodu i próba usunięcia zależności cyklicznej. Możesz na przykład stwierdzić, że masz:
Plik a.py
from b import B
class A:
@staticmethod
def save_result(result):
print('save the result')
@staticmethod
def do_something_a_ish(param):
A.save_result(A.use_param_like_a_would(param))
@staticmethod
def do_something_related_to_b(param):
B.do_something_b_ish(param)
Plik b.py
from a import A
class B:
@staticmethod
def do_something_b_ish(param):
A.save_result(B.use_param_like_b_would(param))
W takim przypadku wystarczy przenieść jedną statyczną metodę do osobnego pliku, powiedz c.py
:
Plik c.py
def save_result(result):
print('save the result')
pozwoli na usunięcie save_result
metody z A, a tym samym na usunięcie importu A z punktu b:
Plik refaktoryzowany a.py
from b import B
from c import save_result
class A:
@staticmethod
def do_something_a_ish(param):
A.save_result(A.use_param_like_a_would(param))
@staticmethod
def do_something_related_to_b(param):
B.do_something_b_ish(param)
Plik refaktoryzowany b.py
from c import save_result
class B:
@staticmethod
def do_something_b_ish(param):
save_result(B.use_param_like_b_would(param))
Podsumowując, jeśli masz narzędzie (np. Pylint lub PyCharm), które informuje o metodach, które mogą być statyczne, samo nałożenie staticmethod
na nie dekoratora może nie być najlepszym sposobem na wyciszenie ostrzeżenia. Mimo że metoda wydaje się powiązana z klasą, może być lepiej ją oddzielić, zwłaszcza jeśli masz kilka blisko powiązanych modułów, które mogą wymagać tej samej funkcjonalności i zamierzasz ćwiczyć zasady DRY.
Okrągły import może być mylący, ponieważ import ma dwie rzeczy:
Pierwsze jest wykonywane tylko raz, a drugie przy każdym wyciągu z importu. Okrągły import tworzy sytuację, gdy moduł importujący używa importowanego z częściowo wykonanym kodem. W rezultacie nie zobaczy obiektów utworzonych po instrukcji importu. Poniżej pokazano przykładowy kod.
Okrągły import nie jest ostatecznym złem, którego należy unikać za wszelką cenę. W niektórych frameworkach, takich jak Flask, są one dość naturalne, a modyfikacja kodu w celu ich wyeliminowania nie poprawia kodu.
main.py
print 'import b'
import b
print 'a in globals() {}'.format('a' in globals())
print 'import a'
import a
print 'a in globals() {}'.format('a' in globals())
if __name__ == '__main__':
print 'imports done'
print 'b has y {}, a is b.a {}'.format(hasattr(b, 'y'), a is b.a)
b.by
print "b in, __name__ = {}".format(__name__)
x = 3
print 'b imports a'
import a
y = 5
print "b out"
a.py
print 'a in, __name__ = {}'.format(__name__)
print 'a imports b'
import b
print 'b has x {}'.format(hasattr(b, 'x'))
print 'b has y {}'.format(hasattr(b, 'y'))
print "a out"
wyjście main.py python z komentarzami
import b
b in, __name__ = b # b code execution started
b imports a
a in, __name__ = a # a code execution started
a imports b # b code execution is already in progress
b has x True
b has y False # b defines y after a import,
a out
b out
a in globals() False # import only adds a to main global symbol table
import a
a in globals() True
imports done
b has y True, a is b.a True # all b objects are available
Rozwiązałem problem w następujący sposób i działa dobrze bez żadnych błędów. Rozważ dwa pliki a.py
i b.py
.
Dodałem to a.py
i zadziałało.
if __name__ == "__main__":
main ()
import b
y = 2
def main():
print ("a out")
print (b.x)
if __name__ == "__main__":
main ()
import a
print ("b out")
x = 3 + a.y
Otrzymałem wynik
>>> b out
>>> a out
>>> 5
Ok, myślę, że mam całkiem fajne rozwiązanie. Powiedzmy, że masz plik a
i plik b
. Masz def
lub class
w pliku b
, który chcesz użyć w module a
, ale trzeba coś innego, albo def
, class
lub zmienna z pliku a
, który trzeba w swojej definicji lub klasy w pliku b
. To, co możesz zrobić, to na dole pliku a
, po wywołaniu funkcji lub klasy w pliku, a
która jest potrzebna w pliku b
, ale przed wywołaniem funkcji lub klasy z pliku b
, które potrzebujesz do pliku a
, powiedz import b
wtedy, a oto kluczowa część , we wszystkich definicjach lub klasach w pliku, b
które wymagają pliku def
lub class
z plikua
(nazwijmy to CLASS
), mówiszfrom a import CLASS
Działa to, ponieważ można zaimportować plik b
bez wykonywania przez Pythona żadnej z instrukcji importu w pliku b
, a tym samym uniknąć importu cyklicznego.
Na przykład:
class A(object):
def __init__(self, name):
self.name = name
CLASS = A("me")
import b
go = B(6)
go.dostuff
class B(object):
def __init__(self, number):
self.number = number
def dostuff(self):
from a import CLASS
print "Hello " + CLASS.name + ", " + str(number) + " is an interesting number."
Voila
from a import CLASS
tak naprawdę nie pomija wykonywania całego kodu w a.py. Oto, co się naprawdę dzieje: (1) Cały kod w pliku a.py jest uruchamiany jako specjalny moduł „__main__”. (2) W import b
momencie uruchamiany jest kod najwyższego poziomu w b.py (definiowanie klasy B), a następnie kontrola powraca do „__main__”. (3) „__main__” ostatecznie przekazuje kontrolę go.dostuff()
. (4) kiedy DoStuff () przychodzi import a
, to działa cały kod w a.py ponownie , tym razem jako modułu „a”; następnie importuje obiekt CLASS z nowego modułu „a”. Tak naprawdę działałoby to równie dobrze, gdybyś używał import a
gdziekolwiek w b.py.