要理解循環(huán)依賴關系,您需要記住Python本質上是一種腳本語言。方法外部語句的執(zhí)行在編譯時發(fā)生。導入語句就像方法調用一樣執(zhí)行,要理解它們,您應該像方法調用一樣考慮它們。
當您進行導入時,所發(fā)生的事情取決于您要導入的文件是否已經存在于模塊表中。如果使用,Python將使用符號表中當前的任何內容。否則,Python將開始讀取模塊文件,編譯/執(zhí)行/導入它在那里找到的任何內容。編譯時引用的符號是否被找到,取決于它們是否已經被看到,或者編譯器還沒有看到它們。
假設您有兩個源文件:
文件X.py
def X1:
return "x1"from Y import Y2def X2:
return "x2"
文件Y.py
def Y1:
return "y1"from X import X1def Y2:
return "y2"
現在假設您編譯了X.py文件。編譯器首先定義X1方法,然后點擊X.py中的import語句。這導致編譯器暫停X.py的編譯并開始編譯Y.py。此后不久,編譯器將命中Y.py中的import語句。由于X.py已經在模塊表中,Python使用現有的不完整X.py符號表來滿足任何請求的引用。在X.py中的import語句之前出現的任何符號現在都在符號表中,但是后面的任何符號都不是。因為X1現在出現在IMPORT語句之前,所以它已成功導入。然后Python繼續(xù)編譯Y.py。在這樣做時,它定義了Y2并完成了對Y.py的編譯。然后繼續(xù)編譯X.py,并在Y.py符號表中找到Y2。編譯最終完成w/o錯誤。
如果嘗試從命令行編譯Y.py,則會發(fā)生非常不同的情況。在編譯Y.py時,編譯器在定義Y2之前點擊import語句。然后開始編譯X.py。很快,它就會命中X.py中的import語句,該語句需要Y2。但是Y2沒有定義,所以編譯失敗。
請注意,如果您修改X.py以導入Y1,編譯將始終成功,無論您編譯的是哪個文件。但是,如果您修改文件Y.py以導入符號X2,則兩個文件都不會編譯。
當模塊X或X導入的任何模塊可能導入當前模塊時,請不要使用:
from X import Y
當您認為可能存在循環(huán)導入時,您也應該避免編譯時引用其他模塊中的變量??紤]一下看似無辜的代碼:
import X
z = X.Y
假設模塊X在這個模塊導入X之前導入這個模塊。進一步假設Y是在導入語句之后在X中定義的。在導入該模塊時,將不會定義Y,您將得到一個編譯錯誤。如果這個模塊首先導入Y,您就可以逃脫它。但是,當您的一個同事無意地更改了第三個模塊中定義的順序時,代碼就會中斷。
在某些情況下,您可以通過將導入語句向下移到其他模塊所需的符號定義下面來解決循環(huán)依賴關系。在上面的例子中,導入語句之前的定義永遠不會失敗。導入語句后的定義有時會失敗,這取決于編譯的順序。您甚至可以將導入語句放在文件的末尾,只要編譯時不需要導入的符號。
請注意,在模塊中向下移動導入語句會掩蓋您正在做的事情。在模塊頂部加上一個注釋來彌補這一點,如下所示:
#import X (actual import moved down to avoid circular dependency)
一般來說,這是一個不好的做法,但有時是很難避免的。