如何處理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.