讓我們編寫的程式碼盡可能清晰且易於閱讀非常重要,這樣任何不熟悉程式碼庫的人都能夠輕鬆理解它的功能。在處理物件導向的 Python 程式碼時,使用 dunder 方法(也稱為魔術方法)是實現此目的的有用方法。它們允許我們的使用者定義的類別使用 Python 的內建和原始構造 - 例如+
、 *
、 /
和其他運算符,以及new
或len
等關鍵字 - 這通常比類別方法鏈直觀得多。
如果您使用 Python 程式設計一段時間,您可能會遇到名稱以成對下劃線開頭和結尾的奇怪方法。如果您使用過 Python 類,您甚至可能熟悉一種特定方法: __init__
,它充當建構函數,並在初始化類別時呼叫。
__init__
是 dunder(雙底線)方法的一個範例,也稱為魔術方法。它們為我們提供了一種方法來「告訴」Python 如何處理在我們的自訂類別上完成的原始操作,例如加法或相等檢查。例如,當您嘗試使用「+」運算子新增自訂類別的兩個實例時,Python 解釋器會進入類別的定義內部並尋找__add__
魔術方法的實作。如果實現了,它就會執行其中的程式碼,就像任何其他「常規」方法或函數一樣。
讓我們實作一個 Time period 類,看看如何使用 Python 的 Magic Method 使其變得更清晰、更具可讀性。為簡單起見,我們的課程將僅處理小時和分鐘,並將提供一些基本功能,例如加入和比較時間段。
此類的基本實作可能如下所示:
class TimePeriod:
def __init__(self, hours=0, minutes=0):
self.hours = hours
self.minutes = minutes
def add(self, other):
minutes = self.minutes + other.minutes
hours = self.hours + other.hours
if minutes >= 60:
minutes -= 60
hours += 1
return TimePeriod(hours, minutes)
def greater_than(self, other):
if self.hours > other.hours:
return True
elif self.hours < other.hours:
return False
elif self.minutes > other.minutes:
return True
else:
return False
然後我們可以像這樣建立我們的類別的實例:
time_i_sleep = TimePeriod(9, 0)
time_i_work = TimePeriod(0, 30)
並對它們執行如下操作:
print(time_i_sleep.greater_than(time_i_work)) # Should print True
這段程式碼在功能上沒有任何問題。它完全按照預期工作並且沒有錯誤。但是,如果我們想要執行更複雜的操作,例如將兩個時間段的總和與另外兩個時間段的總和進行比較,該怎麼辦?
time_i_sleep.add(time_i_watch_netflix).greater_than(time_i_work.add(time_i_do_chores))
嗯,看起來不再那麼棒了,對嗎?讀者必須深入閱讀每個單字才能理解程式碼的作用。
聰明的開發人員可能會將兩個總和分成一對臨時變數,然後進行比較。這確實提高了程式碼的清晰度:
time_spent_unproductively = time_i_sleep.add(time_i_watch_netflix)
time_spent_productively = time_i_work.add(time_i_do_chores)
time_spent_unproductively.greater_than(time_spent_productively)
但最好的解決方案來自於實現 Python 的神奇方法並將我們的邏輯移入其中。在這裡,我們將實作對應於「+」和「>」運算子的__add__
和__gt__
方法。現在讓我們試試看:
class TimePeriod:
def __init__(self, hours=0, minutes=0):
self.hours = hours
self.minutes = minutes
def __add__(self, other):
minutes = self.minutes + other.minutes
hours = self.hours + other.hours
if minutes >= 60:
minutes -= 60
hours += 1
return TimePeriod(hours, minutes)
def __gt__(self, other):
if self.hours > other.hours:
return True
elif self.hours < other.hours:
return False
elif self.minutes > other.minutes:
return True
else:
return False
現在,我們可以將複雜的操作重寫為:
(time_i_sleep + time_i_watch_netflix) > (time_i_work + time_i_do_chores)
我們開始吧!由於我們現在可以使用“+”和“>”等易於辨識的符號,因此更加清晰、更舒適。現在,任何讀過我們程式碼的人一眼就能看出它到底做了什麼。
如果我們想要比較兩個 TimePeriod 物件並使用「==」運算子檢查它們是否相等怎麼辦?我們可以簡單地實作eq方法,如下所示:
def __eq__(self, other):
return self.hours == other.hours and self.minutes == other.minutes
Python 的魔法方法也不只限於算術和比較運算。您可能經常遇到的最有用的此類方法之一是__str__
,它允許您建立類別的易於閱讀的字串表示形式。
def __str__(self):
return f"{self.hours} hours, {self.minutes} minutes"
您可以透過呼叫str(time_period)
來取得此字串表示形式,這在偵錯和日誌記錄中很有用。
如果我們願意,我們甚至可以將我們的類別變成一本字典!
def __getitem__(self, item):
if item == 'hours':
return self.hours
elif item == 'minutes':
return self.minutes
else:
raise KeyError()
這將允許我們使用字典存取語法 [] 來存取時間段中的小時(使用 ['hours'])和分鐘(使用 ['minutes'])。在所有其他情況下,它都會引發 KeyError,表示該金鑰不存在。
Python dunder 方法的完整清單可在Python 語言參考中找到。在這裡,我列出了一些最有趣的內容。歡迎在評論中提出補充建議。
__new__
:雖然__init__
方法(我們已經熟悉的)在初始化類別的實例時被呼叫,但__new__
方法在實際建立實例時更早被呼叫。您可以在這裡閱讀更多相關資訊。
__call__
:__call__
方法允許我們的類別的實例是可呼叫的,就像方法或函數一樣!此類可呼叫類別在 Django 中廣泛使用,例如在中間件中。
__len__
:這允許您定義 Python 內建len()
函數的實作。
__repr__
:這與__str__
魔術方法類似,它允許您定義類別的字串表示形式。然而,不同之處在於__str__
面向最終用戶,並提供更用戶友好的非正式字串,而__repr__
面向開發人員,可能包含有關類內部狀態的更複雜資訊。您可以在此處閱讀有關此差異的更多資訊。
__setitem__
:我們已經了解了__getitem__
方法。 __setitem__
是同一枚硬幣的另一面 - 前者允許取得與鍵對應的值,後者允許設定一個值。
__enter__
和__exit__
:感謝 Waylon Walker 對此評論的評論。這兩種方法一起使用時,允許您的類別用作上下文管理器,這是確保重要程式碼(尤其是在處理文件等外部資源時)始終執行的強大方法。
我們已經看到,對程式碼進行一些小的調整可以在清晰度和可讀性方面帶來巨大的改進,並讓我們能夠使用強大的 Python 語言功能。當然,在這篇文章中,我們只觸及了魔術方法可以為我們提供的功能的表面,因此我建議您透過以下連結來發現有趣的新使用者案例及其可能性。
如果您在本文中發現任何錯誤或想要提出任何類型的改進建議,請隨時在下面發表評論。
圖片由Gerald Friedrich在Pixabay上發布
原文出處:https://dev.to/bikramjeetsingh/how-to-write-better-python-classes-using-magic-methods-4166