如何處理 Python 類之間的迴圈依賴?


在本文中,我們將討論如何處理 Python 類之間的迴圈依賴。首先,讓我們瞭解什麼是迴圈依賴。

當兩個或多個模組相互依賴時,這被稱為迴圈依賴。這是因為每個模組都是根據另一個模組定義的。

以下是迴圈依賴的示例

functionE():
   functionF()

以及

functionF():
   functionE()

上面顯示的程式碼清楚地顯示了迴圈依賴。FunctionA() 呼叫 functionB(),後者依賴於它,而 functionB() 呼叫 functionA()。這種迴圈依賴有一些明顯的問題,我們將在下一節中更詳細地討論。


迴圈依賴的問題

迴圈依賴可能會導致程式碼出現各種問題。例如,它可能導致模組之間緊密耦合,這將限制程式碼重用。這方面也使長期程式碼維護更具挑戰性。

迴圈依賴也可能是記憶體洩漏、無限遞迴和級聯效應等問題的根源。當您的程式碼包含迴圈依賴時,解決它產生的許多可能問題可能非常具有挑戰性,如果您不小心,這種情況可能會發生。

示例

當參與迴圈引用的任何物件的類具有唯一的 __del__ 函式時,就會出現問題。以下是一個顯示迴圈依賴出現問題的示例:

class Python: def __init__(self): print("Object Python is Created") def __del__(self): print("Object Python is Destroyed") class Program: def __init__(self): print("Object Program is Created") def __del__(self): print("Object Program is Destroyed") #create the two objects Py = Python() Pr = Program() #set up the circular reference Py.Pr = Pr Pr.Py = Py #delete the objects del Py del Pr

輸出

在這裡,Py 和 Pr 物件都有一個自定義的 __del__ 函式,並且相互持有引用。最終,當我們嘗試手動刪除物件時,__del__ 方法沒有被呼叫,這表明物件沒有被銷燬,而是導致了記憶體洩漏。

在這種情況下,Python 的垃圾收集器無法收集物件以進行記憶體清理,因為它不確定以什麼順序呼叫 __del__ 函式。

Object Python is Created
Object Program is Created
Object Python is Destroyed
Object Program is Destroyed

修復 Python 中迴圈依賴中的記憶體洩漏

迴圈引用會導致記憶體洩漏,可以透過兩種方式避免,即手動清除每個引用和使用 Python 中的weakref() 函式。

手動刪除每個引用不是一個理想的選擇,因為weakref() 消除了程式設計師需要考慮刪除引用的時間的需要。

在 Python 中,weakref 函式提供了一個弱引用,它不足以維持物件的生存期。當對物件的唯一剩餘引用是弱引用時,該物件可以被垃圾收集器自由銷燬,以便其記憶體可以被另一個物件使用。

示例

以下示例顯示瞭如何修復迴圈依賴中的記憶體洩漏:

import weakref class Python: def __init__(self): print("Object Python is Created") def __del__(self): print("Object Python is Destroyed") class Program: def __init__(self): print("Object Program is Created") def __del__(self): print("Object Program is Destroyed") #create the two objects Py = Python() Pr = Program() #set up the weak circular reference Py.Pr = weakref.ref(Pr) Pr.Py = weakref.ref(Py) #delete the objects del Py del Pr

輸出

正如您所看到的,這次使用了兩種 __del__ 方法,證明物件已成功從記憶體中刪除。

Object Python is Created
Object Program is Created
Object Python is Destroyed
Object Program is Destroyed

透過迴圈匯入的迴圈依賴

Python 中的 import 語句會建立迴圈匯入,這是一種迴圈依賴。

示例

以下示例說明了這一點。假設我們建立了 3 個 Python 檔案,如下所示:

Example1.py

# module3 import module4 def func8(): module4.func4() def func9(): print('Welcome to TutorialsPoint')

Example2.py

# module4 import module4 def func4(): print('Thank You!') module3.func9()

Example3.py

# __init__.py import module3 module3.func8()

Python 在匯入模組時檢查模組登錄檔以檢視它是否已被匯入。如果模組已被註冊,Python 將使用快取中先前存在的物件。模組登錄檔是一個已初始化模組的表,使用模組名稱作為索引。sys.modules 提供對該表的訪問。

如果模組未註冊,Python 會找到它,根據需要初始化它,然後在新模組的名稱空間中執行它。

在上面的示例中,Python 在到達 import module4 時載入並執行。但是,module3 也需要 module4,因為它定義了 func8()。

輸出

當 func4() 嘗試呼叫 module3 中的 func9() 時,問題就出現了。func9() 尚未定義並返回錯誤,因為 module3 先載入,它在訪問它之前載入了 module4:

$ python __init__.py
Thank You!
Traceback (most recent call last):
   File "__init__.py", line 3, in 
   Module3.func8()
File "C:\Users\Lenovo\Desktop\module3\__init__.py", line 5, in func8
   Module4.func4()
File "C:\Users\Lenovo\Desktop\module4\__init__.py", line 6, in func4
   module4.func9()
AttributeError: 'module' object has no attribute 'func9

修復上述迴圈依賴

迴圈匯入通常是不良設計的結

有時,將兩個模組合併到一個更大的模組中是一個簡單的選擇。

示例

來自上述示例的最終程式碼將類似於上面給出的解釋:

# module 3 & 4 def func8(): func4() def func4(): print('Welcome to TutorialsPoint') func9() def func9(): print('Thank You!') func8()

輸出

以下是上述程式碼的輸出:

Welcome to TutorialsPoint
Thank You!

注意 - 但是,如果兩個模組已經包含大量程式碼,合併後的模組可能包含一些不相關的函式(緊密耦合),並且可能會大幅增長。

如果它不起作用,另一種選擇可能是延遲匯入 module4,只在需要時匯入它。為此,請將 module4 的匯入包含在 func8() 的定義中,如下所示:

# module 3 def func8(): import module4 module4.func4() def func9(): print('Thank You!')

在上述情況下,Python 將能夠載入 module3 中的所有函式,並且僅在需要時載入 module4。

通常的做法是在模組(或指令碼)的開頭插入所有 import 語句,但這不是必需的,“這種方法不會違反 Python 語法。

更新於:2022年11月23日

4K+ 瀏覽量

啟動你的職業生涯

完成課程獲得認證

開始學習
廣告
© . All rights reserved.