2014-03-05 72 views
53

所以我得到這個錯誤Python循環導入?

Traceback (most recent call last): 
    File "/Users/alex/dev/runswift/utils/sim2014/simulator.py", line 3, in <module> 
    from world import World 
    File "/Users/alex/dev/runswift/utils/sim2014/world.py", line 2, in <module> 
    from entities.field import Field 
    File "/Users/alex/dev/runswift/utils/sim2014/entities/field.py", line 2, in <module> 
    from entities.goal import Goal 
    File "/Users/alex/dev/runswift/utils/sim2014/entities/goal.py", line 2, in <module> 
    from entities.post import Post 
    File "/Users/alex/dev/runswift/utils/sim2014/entities/post.py", line 4, in <module> 
    from physics import PostBody 
    File "/Users/alex/dev/runswift/utils/sim2014/physics.py", line 21, in <module> 
    from entities.post import Post 
ImportError: cannot import name Post 

,你可以看到,我用同樣的import語句進一步上漲和它的作品?有沒有關於循環導入的不成文規定?我如何在調用堆棧下面繼續使用同一個類?

+0

另請參見:[「Python 2和Python 3中的循環導入:它們何時致命?它們何時工作?」](https://gist.github.com/datagrok/40bf84d5870c41a77dc6)。 –

回答

77

我認爲jpmc26目前公認的答案歸結過於依賴進口的圓形。如果你正確設置它們,它們可以很好地工作。

這樣做最簡單的方法是使用import my_module語法,而不是from my_module import some_object。前者幾乎總是有效的,即使my_module包括進口我們回來。如果my_object已在my_module中定義,後者僅在循環導入時可能不適用。

具體到您的案例:嘗試更改entities/post.pyimport physics,然後參考physics.PostBody而不是直接PostBody。同樣,將physics.py更改爲import post,然後使用post.Post而不僅僅是Post

+3

此答案與相對導入兼容嗎? – Joe

+5

這是爲什麼發生? –

+1

說非''from'語法總是能工作是錯誤的。如果我有'class A(object):pass; C模塊中的類(b.B):pass'和模塊b中的類B(a.A):pass'然後循環導入仍然是一個問題,這不起作用。 – CrazyCasta

38

當導入首次一個模塊(或它的部件),該模塊中的代碼被順序地與任何其他代碼執行;例如,它的處理方式與函數的主體沒有任何區別。一個import只是像任何其他(賦值語句,一個函數調用,defclass)命令。假設你的進口發生在腳本的頂部,那麼這裏發生的事情:

  • 當您嘗試從world導入World,該world腳本被執行。
  • world腳本進口Field,這將導致entities.field腳本得到執行。
  • 這個過程一直持續,直到你到達entities.post腳本,因爲你試圖導入Post
  • entities.post腳本導致,因爲它試圖導入PostBody
  • 最後,physics,嘗試從entities.post
  • 導入 Post要執行 physics模塊
  • 我不確定entities.post模塊是否存在於內存中,但它確實沒關係。任一模塊不在存儲器中,或者該模塊還沒有一個Post構件,因爲它尚未完成執行,以限定Post
  • 無論哪種方式,發生錯誤時,因爲Post是不存在要導入

所以,不,它不是「工作進一步向上調用堆棧」。這是錯誤發生位置的堆棧跟蹤,這意味着它錯誤地嘗試導入該類中的Post。你不應該使用循環進口。最好的情況是它的收益可以忽略不計(通常,沒有的好處),並且會導致這樣的問題。它負擔任何開發商維護它,迫使他們走在蛋殼上,以避免打破它。重構您的模塊組織。

+0

是的,我認爲這是這樣的。它只是在該文件中的類定義,我只需要導入,所以我可以進行類檢查,即'if userData == Post:': - ? – CpILL

+1

應該是'isinstance(userData,Post)'。無論如何,你沒有選擇。循環導入不起作用。你有循環進口的事實對我來說是一種代碼味道。它表明你有一些功能應該移出到第三個模塊。如果不考慮兩個班級,我都說不出什麼。 – jpmc26

+3

@CpILL過了一段時間,我發現了一個非常黑客的選擇。如果你現在無法做到這一點(由於時間限制或你有什麼),那麼你*可以* *在你使用它的方法中進行本地導入。直到函數被調用時,'def'中的函數體纔會被執行,所以直到你實際調用該函數纔會導致導入。屆時,「進口」應該起作用,因爲其中一個模塊在呼叫之前已經完全導入。這是一個非常令人厭惡的黑客行爲,它不應該留在你的代碼基礎上很長時間。 – jpmc26

6

對於那些你們誰和我一樣,都從Django的這個問題,你應該知道的文檔提供瞭解決方案: https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey

」 ......要引用另一個應用程序定義的模型,你。可以明確指定完整的應用標籤的模型。例如,如果製造商模型上面在另一個應用程序稱爲生產的定義,你需要使用:

class Car(models.Model): 
    manufacturer = models.ForeignKey(
     'production.Manufacturer', 
     on_delete=models.CASCADE, 
) 

這種參考是有用的解決循環進口時兩個應用程序之間的依賴性 ...「

+0

我知道我不應該用評論來說「謝謝」,但這一直困擾着我幾個小時。謝謝你,謝謝你,謝謝你!!! – MikeyE

15

爲了理解循環依賴關係,您需要記住,Python本質上是一種腳本語言,在編譯時執行語句以外的語句,導入語句就像方法調用一樣執行,並且理解它們你應該像方法調用一樣考慮它們

當你做一個導入時,會發生什麼事取決於你導入的文件是否已經存在於模塊表中,如果有的話,Python會使用符號表中的任何東西。如果不是,Python開始讀取模塊文件,編譯/執行/導入它在那裏找到的任何東西。在編譯時引用的符號是否被找到,取決於它們是否被看到或者還沒有被看到由編譯器。

想象一下,你有兩個源文件:

文件X.py

def X1: 
    return "x1" 

from Y import Y2 

def X2: 
    return "x2" 

文件Y.py

def Y1: 
    return "y1" 

from X import X1 

def Y2: 
    return "y2" 

現在假設您編譯文件X.py.編譯器首先定義方法X1,然後點擊X.py中的import語句。這會導致編譯器暫停編譯X.py並開始編譯Y.py.此後不久,編譯器命中Y.py中的導入語句。由於X.py已經在模塊表中,因此Python使用現有的不完整的X.py符號表來滿足所請求的任何引用。任何出現在X.py中的導入語句之前的符號現在都在符號表中,但之後的任何符號都不是。由於X1現在出現在導入語句之前,因此它已成功導入。然後Python繼續編譯Y.py.在這樣做時,它定義了Y2並完成了編譯Y.py.然後它恢復編譯X.py,並在Y.py符號表中找到Y2。編譯最終會完成沒有錯誤。

如果嘗試從命令行編譯Y.py,會發生非常不同的情況。編譯Y.py時,編譯器在定義Y2之前觸發導入語句。然後它開始編譯X.py.不久它就會觸發需要Y2的X.py中的import語句。但是Y2是未定義的,所以編譯失敗。

請注意,如果您修改X.py來導入Y1,無論您編譯哪個文件,編譯都會成功。但是,如果您修改文件Y.py來導入符號X2,那麼這兩個文件都不會編譯。

任何時候模塊X,或X可能會導入當前模塊中引入任何模塊,不要使用:

from X import Y 

你認爲有可能是一個圓形的進口,你也應該避免編譯時引用的任何時間到其他模塊中的變量。這個模塊導入X.進一步假設的Y X import語句之後被定義之前

import X 
z = X.Y 

假設模塊X導入該模塊:考慮無辜的看着代碼。那麼當這個模塊被導入時,Y將不會被定義,並且你會得到一個編譯錯誤。如果該模塊首先導入Y,則可以避開它。但是當你的一個同事天真地改變第三個模塊中定義的順序時,代碼就會中斷。

在某些情況下,您可以通過將導入語句向下移動到其他模塊所需的符號定義之下來解決循環依賴關係。在上面的例子中,導入語句之前的定義永遠不會失敗。導入語句之後的定義有時會失敗,具體取決於編譯的順序。您甚至可以將導入語句放在文件末尾,只要編譯時不需要導入的符號。

請注意,在模塊中向下移動導入語句會掩蓋您正在執行的操作。彌補這一帶在你的模塊類似下面的頂部評論:

#import X (actual import moved down to avoid circular dependency) 

總的來說,這是一個不好的做法,但有時是難以避免的。

+1

我不認爲Python中存在編譯器或編譯時間 – pkqxdd

+2

Python *不具有編譯器,而*編譯爲@pkqxdd,編譯通常隱藏於用戶之外。這可能有點令人困惑,但作者很難清楚地描述發生了什麼,而沒有提及Python的某些模糊的「編譯時間」。 – Hank