Aby zrozumieć zależności cykliczne, należy pamiętać, że Python jest zasadniczo językiem skryptowym. Wykonywanie instrukcji poza metodami odbywa się w czasie kompilacji. Instrukcje importu są wykonywane tak jak wywołania metod i aby je zrozumieć, należy myśleć o nich jak o wywołaniach metod.
Podczas importowania to, co się stanie, zależy od tego, czy importowany plik już istnieje w tabeli modułu. Jeśli tak, Python używa tego, co aktualnie znajduje się w tablicy symboli. Jeśli nie, Python zaczyna czytać plik modułu, kompilując / wykonując / importując wszystko, co tam znajdzie. Symbole, do których odwołuje się w czasie kompilacji, zostały znalezione lub nie, w zależności od tego, czy zostały one zauważone, czy nie zostały jeszcze zauważone przez kompilator.
Wyobraź sobie, że masz dwa pliki źródłowe:
Plik X.py
def X1:
return "x1"
from Y import Y2
def X2:
return "x2"
Plik Y.py
def Y1:
return "y1"
from X import X1
def Y2:
return "y2"
Teraz załóżmy, że kompilujesz plik X.py. Kompilator rozpoczyna od zdefiniowania metody X1, a następnie trafia na instrukcję import w X.py. Powoduje to, że kompilator wstrzymuje kompilację X.py i rozpoczyna kompilację Y.py. Wkrótce potem kompilator trafia na instrukcję import w Y.py. Ponieważ X.py jest już w tabeli modułów, Python używa istniejącej niekompletnej tabeli symboli X.py, aby spełnić wszelkie wymagane odniesienia. Wszelkie symbole pojawiające się przed instrukcją import w X.py znajdują się teraz w tablicy symboli, ale żadne symbole po nich już nie. Ponieważ X1 pojawia się teraz przed instrukcją importu, zostało pomyślnie zaimportowane. Python wznawia kompilację Y.py. Robiąc to, definiuje Y2 i kończy kompilację Y.py. Następnie wznawia kompilację X.py i znajduje Y2 w tabeli symboli Y.py. Kompilacja ostatecznie kończy się bez błędu.
Coś zupełnie innego dzieje się, jeśli spróbujesz skompilować Y.py z wiersza poleceń. Podczas kompilowania Y.py kompilator trafia w instrukcję import, zanim zdefiniuje Y2. Następnie zaczyna kompilować X.py. Wkrótce trafia na instrukcję import w X.py, która wymaga Y2. Ale Y2 jest niezdefiniowane, więc kompilacja kończy się niepowodzeniem.
Zwróć uwagę, że jeśli zmodyfikujesz X.py, aby zaimportować Y1, kompilacja zawsze się powiedzie, bez względu na to, który plik kompilujesz. Jednak jeśli zmodyfikujesz plik Y.py, aby zaimportować symbol X2, żaden plik nie zostanie skompilowany.
Za każdym razem, gdy moduł X lub jakikolwiek moduł importowany przez X może zaimportować bieżący moduł, NIE używaj:
from X import Y
Za każdym razem, gdy myślisz, że może nastąpić cykliczny import, powinieneś również unikać kompilacji odniesień do zmiennych w innych modułach. Rozważ niewinnie wyglądający kod:
import X
z = X.Y
Załóżmy, że moduł X importuje ten moduł, zanim ten moduł zaimportuje X. Ponadto przypuśćmy, że Y jest zdefiniowane w X po instrukcji importu. Wtedy Y nie zostanie zdefiniowane podczas importowania tego modułu i pojawi się błąd kompilacji. Jeśli ten moduł zaimportuje Y jako pierwszy, możesz sobie z tym poradzić. Ale kiedy jeden z twoich współpracowników niewinnie zmieni kolejność definicji w trzecim module, kod się zepsuje.
W niektórych przypadkach można rozwiązać zależności cykliczne, przenosząc instrukcję importu w dół poniżej definicji symboli wymaganych przez inne moduły. W powyższych przykładach definicje przed instrukcją import nigdy nie zawodzą. Definicje po instrukcji import czasami zawodzą, w zależności od kolejności kompilacji. Możesz nawet umieścić instrukcje importu na końcu pliku, o ile żaden z importowanych symboli nie jest potrzebny w czasie kompilacji.
Zwróć uwagę, że przeniesienie instrukcji importu w dół w module przesłania to, co robisz. Skompensuj to komentarzem u góry modułu, podobnym do następującego:
Ogólnie jest to zła praktyka, ale czasami trudno jej uniknąć.