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 foowewnątrz bari import barwewną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 abci 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
importinstrukcji. 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 foojest w fazie inicjalizacji bar, a rzeczywiste funkcje w barnie 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ę bponownie, ponieważ już istnieje w module dict.
Jeśli próbujesz uzyskać dostęp b.xz apodczas 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.xdostępu do niego linia x = 3nie 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 ImportErrorbłędy, z powodu sposobu, w jaki import()wywoływane są instrukcje najwyższego poziomu całego pliku.
Te ImportErrorsmogą 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:00oto 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ć SimplifiedImageSerializeri jeśli ImportErrorzostanie 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 barsię foo.pydo końcowego
bari foooba muszą użyć gX, „najczystszym” rozwiązaniem jest umieszczenie gXinnego modułu, posiadanie obu fooi barimportowanie tego modułu. (najczystszy w tym sensie, że nie ma ukrytych zależności semantycznych).
barnie można nawet znaleźć gXw foo. cykliczny import sam w sobie jest w porządku, ale po prostu gXnie 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 bale 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_resultmetody 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 staticmethodna 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.pyi b.py.
Dodałem to a.pyi 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 ai plik b. Masz deflub classw pliku b, który chcesz użyć w module a, ale trzeba coś innego, albo def, classlub 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, aktó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, bktóre wymagają pliku deflub classz plikua(nazwijmy to CLASS), mówiszfrom a import CLASS
Działa to, ponieważ można zaimportować plik bbez 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 CLASStak 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 bmomencie 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 agdziekolwiek w b.py.