[英]Remove python circular import
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
。 我了解到可以在方法定義中移動導入語句,以防止出現此錯誤。 但是我仍然想知道,在這種情況下是否有辦法消除循環導入,或者是否有必要(對於一個好的設計)?
減輕圓度的另一種方法是更改導入樣式。 from story import Story
更改為import story
,然后將類稱為story.Story
。 import story
。 由於您僅引用方法內部的類,因此在調用該方法之前,不需要訪問該類,此時導入將成功完成。 (您可能必須在其中一個或兩個模塊中進行此更改,具體取決於先導入哪個模塊。)
但是,該設計確實有些奇怪。 您的設計要使User
和Story
類緊密耦合-不能在沒有另一個的情況下使用。 在這種情況下,通常將它們放在同一模塊中會更有意義。
在這種情況下,最明顯的解決方案是通過更改接口來完全打破對User
類的依賴,以使Story
構造函數接受實際的User
而不是user_id
。 這也導致了更有效的設計:例如,如果用戶有很多故事,則可以將相同的對象分配給所有這些構造函數。
除此之外,整個模塊(即story
和user
而不是成員)的導入都應該起作用-第一個導入的模塊在第二個導入時將顯示為空; 但是,這並不重要,因為這些模塊的內容不在全局范圍內使用。
這比在方法中導入要好一些。 僅在模塊全局查找( story.Story
)上,方法內的導入會產生大量開銷,因為需要為每個方法調用完成此操作; 在一個簡單的情況下,開銷似乎至少是30倍。
網路上有許多這類python
圓形匯入問題。 我選擇對此線程做出貢獻,因為該查詢包含Ray Hettinger的注釋,該注釋使循環導入的用例合法化,但是建議了一種我認為不是特別好的做法的解決方案-將導入移至方法。
除Hettinger的權限外,對於共同異議有三點免責聲明:
此外,我認為可維護性和可讀性要求將導入分組到文件的頂部,每個所需名稱僅進行一次from module import name
,並且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
的import語句替換為:
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 ...
語法,清楚,簡潔地記錄了循環導入問題,從而減輕了將來可能需要的維護工作。 每當重構確實不是一個好主意時都使用它。
正如BrenBarn所說,最明顯的解決方案是將User和Story放在同一個模塊中,如果用戶應該對Story有所了解,這是很有意義的。 現在,如果確實需要將它們放在不同的模塊中,則還可以在story.py中對Userp進行猴子補丁添加get_stories
方法。 這是可讀性/去耦權衡...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.