2013-11-15 117 views
8

user.py:刪除蟒蛇圓形進口

from story import Story 

class User: 
    ... 
    def get_stories(self): 
     story_ids = [select from database] 
     return [Story.get_by_id(id) for id in story_ids] 

story.py

from user import User 

class Story: 
    ... 
    def __init__(self, id, user_id, content): 
     self.id = id 
     self.user = User.get_by_id(user_id) 
     self.content = content 

你可以看到,有此計劃,這將導致一個ImportError圓形進口。我知道我可以在方法定義中移動import語句來防止這個錯誤。但我仍然想知道,在這種情況下是否有辦法刪除循環導入,或者是否有必要(對於一個好的設計)?

+0

沒有必要刪除良好設計的循環導入。將導入移到方法定義中是推遲導入的合理方法。 –

回答

1

減輕圓形度的另一種方法是更改​​導入樣式。將from story import Story更改爲import story,然後將該類別稱爲story.Story。由於您只能在方法內引用類,所以在調用方法之前不需要訪問該類,屆時導入將成功完成。 (您可能必須在其中一個或兩個模塊中進行此更改,具體取決於先導入哪個模塊。)

但是,設計看起來有點奇怪。您的設計是這樣的:UserStory類非常緊密耦合 - 兩者都可以在沒有其他的情況下使用。在這種情況下,將它們都放在同一模塊中通常會更有意義。

+0

是的,我覺得這個設計有點奇怪...因爲這是一種常見的情況,我想知道一個好的設計應該是什麼樣子?謝謝 – wong2

+1

那麼,這__isn't__一般情況下:) –

+0

建議的修改不會解決問題,仍然會有循環導入。 –

0

正如BrenBarn所說,最明顯的解決方案是將用戶和故事保持在同一個模塊中,如果用戶應該知道關於故事的任何內容,這是非常合理的。現在,如果你真的需要將它們放在不同的模塊中,你也可以在story.py中添加用戶,以添加get_stories方法。這是一個可讀性/脫鉤權衡...

1

在這種情況下,最明顯的解決方案就是要打破依賴於User類完全,通過改變接口,這樣Story構造函數接受實際User,而不是一個user_id。這也會導致更高效的設計:例如,如果用戶有很多故事,則可以將相同的對象賦予所有這些構造函數。

除此之外,導入整個模塊(即storyuser而不是成員)應該可以工作 - 先導入的模塊在導入第二個模塊時將顯示爲空;然而,這並不重要,因爲這些模塊的內容並未在全球範圍內使用。

這比在一個方法內導入略好。在一個方法中導入相對於模塊全局查找(story.Story)具有很大的開銷,因爲它需要爲每個方法調用完成;似乎在一個簡單的情況下,開銷至少是30倍。

1

在網絡上有一堆這些python循環導入問題。我選擇爲這個線程做出貢獻,因爲這個查詢有一個由Ray Hettinger發表的評論,該評論合法化了一個循環導入的用例,但是建議一個我認爲不是特別好的做法 - 將導入移動到一個方法。

除了赫廷傑的權威,三免責聲明共同反對意見是必要的:

  1. 我從來沒有用Java編程。我沒有試圖做Java風格。
  2. 重構並不總是有用或有效。邏輯API有時會規定一個使遞歸導入引用不可避免的結構。請記住,代碼存在於用戶而不是程序員。
  3. 將相當大的模塊組合會導致可讀性和可維護性問題,這可能比一個或兩個遞歸導入更糟糕。

此外,我相信可維護性和可讀性決定了進口在文件的頂部進行分組,每一個需要的名字只出現一次,那from module import name風格是優選的(除非是在非常短的模塊名稱很多函數,例如gtk),因爲它避免了重複的語言混亂,並且使依賴性明確。

因爲這樣,我會將我自己的用例的簡化版本帶到這裏,並提供我的解決方案。

我有兩個模塊,每個模塊定義許多類。 surface定義了幾何曲面,如平面,球體,雙曲面等。path定義了平面幾何圖形,如直線,圓雙曲線等。從邏輯上講,這些是不同的類別,重構不是API要求的選項。儘管如此,這兩個類別是親密的。

一個有用的操作是交叉兩個表面,例如,兩個平面的交點是一條線,或者一個平面和一個球的交點是一個圓。

如果,例如,在surface.py你實現一個交集操作的返回值所需要的直線前進進口:

from path import Line 

你:

Traceback (most recent call last): 
    File "surface.py", line 62, in <module> 
    from path import Line 
    File ".../path.py", line 25, in <module> 
    from surface import Plane 
    File ".../surface.py", line 62, in <module> 
    from path import Line 
ImportError: cannot import name Line 

幾何,飛機被用來定義路徑,畢竟它們可以以三個(或更多)維度任意定向。追溯可以告訴你正在發生的事情和解決方案。

只需使用替代進口語句surface.py

try: from path import Line 
except ImportError: pass # skip circular import second pass 

操作的序列中的追溯仍在進行之中。只是第二次通過,我們忽略了導入失敗。這並不重要,因爲Line未在模塊級別使用。因此,surface的必要名稱空間將被加載到path中。 path的命名空間解析因此可以完成,允許將其加載到surface中,完成與from path import Line的首次對話。因此,surface的命名空間解析可以繼續並完成,繼續執行其他任何必需的操作。

這是一個簡單而非常清晰的習語。 try: ... except ...語法清楚而簡潔地記錄了循環導入問題,可以緩解未來可能需要的任何維護。只要重構真的是一個壞主意,就使用它。