自定義 Python 類中的運算符和函數重載(上)

自定义 Python 类中的运算符和函数重载(上)

如果你對 Python 中的str對象使用過 + 或 * 運算符,你一定注意到了它的操作與 int 或 float 類型的區別:

自定义 Python 类中的运算符和函数重载(上)

你可能想知道同一內置運算符或函數如何對不同類對象進行不同操作的。這分別稱為運算符重載和函數重載。本文將幫助你瞭解此機制,以便你可以在自己的 Python 類中執行相同的過程,使對象更 Pythonic。

你將瞭解以下內容:

  • 處理 Python 中的運算符和內置函數的 API

  • len 和其他內置函數背後的 "秘密"

  • 如何使你的類能夠使用運算符

  • 如何使你的類與 Python 的內置函數兼容

除此之外你還能看到一個示例類,其中的對象與許多運算符和函數兼容。我們開始吧!

Python 數據模型

假設有一個表示在線訂單的類,具有購物車 (列表) 和顧客 (代表顧客的str或其他類的實例)兩種數據。

在這種情況下,要獲得購物車列表的長度是很自然的。新接觸 Python 的人可能會選擇在他們的類中實現一個叫get_cart_len的方法來執行此項。但是,你也可以配置內置函數 len,以便在給定對象時返回購物車列表的長度。

在另一種情況下, 我們可能需要添加一些東西到購物車。再次,新接觸 Python 的人會想到實現一個 append_to_cart 方法,以添加東西到購物車列表。但是你也可以配置運算符 + ,用它來將新內容添加到購物車中。

Python 使用特殊的方法來做這些。這些特殊方法具有命名約定,其中名稱以兩個下劃線開頭, 後跟一個標識符, 並以另一對下劃線結尾。

本質上, 每個內置函數或運算符都有一個與之對應的特殊方法。例如,對應於 len 有 __len__ ,對應於運算符 + 有__add__。

默認情況下, 大多數內置函數和運算符都不能與自定義類的對象一起使用。必須在類定義中添加相應的特殊方法,才能使對象與內置和運算符兼容。

執行此操作時,與其關聯的函數或運算符的行為將根據方法中定義的方式進行更改。

這正是數據模型(Python 文檔的3節) 幫助你完成的內容。它列出了所有可用的特殊方法,併為你提供了重載內置函數和運算符的方法, 以便你可以在自己的對象上使用它們。

讓我們看看這意味著什麼。

有趣的事實:由於這些方法使用的命名約定, 它們也被稱為dunder 方法,這是對double underscore的縮寫。有時它們也被稱為特殊方法或魔術方法。不過,我們更喜歡叫它dunder 方法!

像 len 和 這樣的內部操作

Python 中的每個類對內置函數和方法都進行了重新定義。將某個類的實例傳遞給內置函數或在實例上使用運算符時,實際上等效於調用具有相關參數的特殊方法。

如果有內置函數func,且這個函數的相應特殊方法是__func__,Python 對函數的調用是obj.__func__,其中obj即對象。在運算符的情況下, 如果有一個運算符 opr 和相應的特殊方法__opr__, Python 會將類似於obj1 obj2的解釋為obj1.__opr__(obj2)。

因此,當你對對象調用 len 時,Python 會將調用以 obj.__len__ 處理。當你對可迭代對象使用 操作符獲取索引上的值時,Python 以 itr.__getitem__(index) 處理它,其中itr是可迭代對象,index是你想獲取的索引。

因此,當你在自己的類中定義這些特殊方法時會重寫與它們關聯的函數或運算符的行為,因為實際 Python 是執行調用你的方法。讓我們深入瞭解一下:

自定义 Python 类中的运算符和函数重载(上)

正如你所看到的,當你使用該函數或其相應的特殊方法時, 將得到相同的結果。事實上,當你使用 dir 獲取 str 對象的屬性和方法列表時,除了str對象上可用的常用方法外,還將在列表中看到這些特殊方法:

自定义 Python 类中的运算符和函数重载(上)

如果在類中未通過特殊方法重新定義內置函數或運算符,則會返回TypeError。

那麼,如何在你的類中使用特殊的方法?

重載內置函數

數據模型中定義的許多特殊方法如 len, abs, hash, divmod等等可用於更改函數的行為。為此,你只需在類中定義相應的特殊方法。讓我們看幾個例子:

使用 len 獲取你對象的長度

若要更改 len 的操作,需要在類中定義特殊方法 __len__ 。當你將自定義類的對象傳遞給 len 時,自定義定義將用於獲取結果。讓我們來實現我們一開始討論的類 len:

自定义 Python 类中的运算符和函数重载(上)

正如你所看到的,現在可以使用 len 來直接獲取購物車的長度。此外,說 "訂單的長度"比調用類似order.get_cart_len的方法更直觀。你的調用既 Pythonic 又直觀。如果沒有定義__len__方法但仍對你的對象調用len,則會遇到TypeError:

自定义 Python 类中的运算符和函数重载(上)

但是,當重載len時,你應該記住 Python 需要函數返回一個整數。如果你的方法是返回一個整數以外的任何東西,你會得到TypeError。這很可能是為了使它與 len 通常用於獲取序列長度這一事實保持一致,而這隻能是一個整數:

自定义 Python 类中的运算符和函数重载(上)

使用 abs

通過定義類中的特殊方法 __abs__ 可以為類的實例重寫內置abs的操作。abs 的返回值沒有限制,而當你的類定義中缺少特殊方法時, 就會返回TypeError。

在表示二維空間中向量的類中,可用 abs 獲取向量的長度。讓我們來看看它的實際過程:

自定义 Python 类中的运算符和函数重载(上)

說 "向量絕對值"比調用類似vector.get_mag的方法更直觀。

使用 str 打印對象

內置的str用於將類的實例強制轉換為str對象,或者更準確地說是獲取對象的用戶友好型的字符串表示形式,而這可以被普通用戶而不是程序員接受。通過在類中定義__str__方法,在傳遞str時可以定義對象應顯示的字符串格式。此外,__str__是 Python 在你對對象調用print時使用的方法。

讓我們在Vector類中實現此目的,使Vector對象的格式化為xi+yj。對y為負值的部分將使用 "迷你語言" 格式處理:

自定义 Python 类中的运算符和函数重载(上)

__str__返回一個str對象是必要的,如果返回類型是非字符串, 則得到TypeError。

使用 repr 表示對象

內置的repr用於獲取對象的解析字符串表示形式。如果一個對象是解析的,這意味著當repr與函數eval一起使用時 Python 能夠從表示形式中重新創建對象。若要定義repr的行為,可以使用特殊方法__repr__。

這也是 Python 用於在 REPL 會話中顯示對象的方法。如果未定義__repr__方法,你將得到類似於<__main__.vector object="" at="">一樣嘗試查看 REPL 會話中的對象內容。讓我們在Vector類裡查看它的作用:

自定义 Python 类中的运算符和函数重载(上)

注:在未定義__str__方法的情況下,Python 使用__repr__方法來打印對象,並在調用str時表示該對象。如果兩種方法都缺失, 則默認為<__main__.vector ...="">。而__repr__是用於在交互式會話中顯示對象的唯一方法。類中缺失它則會默認為<__main__.vector ...="">。

此外,雖然__str__和__repr__有這種區分,許多流行的庫是忽略這種區別的,並混雜使用這兩種方法。

下面推薦我們自己的一篇介紹__repr__和__str__的文章:https://dbader.org/blog/python-repr-vs-str。

使用 bool 獲得對象的布爾值

內置的bool可用於獲取對象的布爾值。若要重新定義其行為,可以使用特殊方法__bool__ (在 Python 2.x 中是__nonzero__) 。

此處定義的行為將在所有需要獲取布爾值的情境 (如if語句) 中確定實例是否為真。

例如,對於上面定義的Order類,如果購物車列表的長度是非零,則可以認為實例是真。這可用於檢查是否應處理訂單:

自定义 Python 类中的运算符和函数重载(上)

注:當在類中未實現特殊方法__bool__時,__len__所返回的值將用作布爾值,其中非零值為真,零值為假。如果兩種方法都未實現,則將該類的所有實例視為真。

有許多更特殊的方法重載內置函數。你可以在文檔中找到它們。討論了其中的一些,接下來讓我們看看運算符。

英文原文:https://realpython.com/operator-function-overloading/
譯者:β


分享到:


相關文章: