簡體   English   中英

刪除python循環導入

[英]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.Storyimport story 由於您僅引用方法內部的類,因此在調用該方法之前,不需要訪問該類,此時導入將成功完成。 (您可能必須在其中一個或兩個模塊中進行此更改,具體取決於先導入哪個模塊。)

但是,該設計確實有些奇怪。 您的設計要使UserStory類緊密耦合-不能在沒有另一個的情況下使用。 在這種情況下,通常將它們放在同一模塊中會更有意義。

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

除此之外,整個模塊(即storyuser而不是成員)的導入都應該起作用-第一個導入的模塊在第二個導入時將顯示為空; 但是,這並不重要,因為這些模塊的內容不在全局范圍內使用。

這比在方法中導入要好一些。 僅在模塊全局查找( story.Story )上,方法內的導入會產生大量開銷,因為需要為每個方法調用完成此操作; 在一個簡單的情況下,開銷似乎至少是30倍。

網路上有許多這類python圓形匯入問題。 我選擇對此線程做出貢獻,因為該查詢包含Ray Hettinger的注釋,該注釋使循環導入的用例合法化,但是建議了一種我認為不是特別好的做法的解決方案-將導入移至方法。

除Hettinger的權限外,對於共同異議有三點免責聲明:

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

此外,我認為可維護性和可讀性要求將導入分組到文件的頂部,每個所需名稱僅進行一次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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM