使用者:Xyy23330121/Python/類的方法與數學運算

來自維基學院


在 Python 中,數學運算是用方法來實現的。方法的返回值就是數學運算的結果。以下將講解各個數學運算所對應的方法。

一元運算[編輯 | 編輯原始碼]

基本一元運算[編輯 | 編輯原始碼]

以下是一元運算的列表:

基本一元運算
名稱 運算符
函數
對應方法
-a a.__neg__(self)
+a a.__pos__(self)
絕對值 abs(a) a.__abs__(self)
按位
取反
~a a.__invert__(self)

這些方法的返回值,應當是讀者希望一元運算返回的結果。

特殊一元運算[編輯 | 編輯原始碼]

除上面的運算之外,還有一些一元運算需要特殊備註:

特殊一元運算
名稱 運算符
函數
對應方法
捨入 round(a) a.__round__(self[, ndigits])
向零
取整
math.trunc(a) a.__trunc__(self)
向下
取整
math.floor(a) a.__floor__(self)
向上
取整
math.ceil(a) a.__ceil__(self)

round(number, ndigits=None) 支持第二個參數,因此對應的 __round__ 方法也應當支持可選的第二個參數。

其它三個方法 math.trunc(x)math.floor(x)math.ceil(x) 都是 math 模組中的函數。在使用時,需要先 import math

二元運算[編輯 | 編輯原始碼]

我們將詳細講解加法運算,然後直接類推到其它種類的運算。

加法[編輯 | 編輯原始碼]

我們有三個方法與加法運算有關。

object.__add__(self, other)[編輯 | 編輯原始碼]

此方法對應 self + other 的情況。該方法返回的值應當為程序員希望 self + other 所得到的結果。

object.__radd__(self, other)[編輯 | 編輯原始碼]

此方法對應 other + self 的情況。該方法返回的值應當為程序員希望 other + self 所得到的結果。

object.__iadd__(self, other)[編輯 | 編輯原始碼]

此方法對應 self += other 的情況。該方法應該直接修改self自身,並返回修改的結果。而非僅返回一個結果。

這並不代表返回的修改結果必須和修改後的self相等。

調用順序[編輯 | 編輯原始碼]

對於 a + b 運算時,會先嘗試調用 a.__add__ 方法。如果調用失敗,且 ab 的類型不相同時,則會嘗試調用 b.__radd__

而對於 a += b 運算時,會先嘗試調用 a.__iadd__ 方法。如果調用失敗,就改為計算 a = a + b,並按照上面規則嘗試調用 a.__add__b.__radd__

所以,只要良好地定義了 __add__ 方法,就可以實現任何類型的加法運算。

其它二元運算[編輯 | 編輯原始碼]

其它二元運算和加法完全是類似的。我們將這些運算總結到以下列表里。

二元運算列表
名稱 運算符
函數
對應方法 對應方法(右) 賦值運算
加法 a+b a.__add__(self, other) b.__radd__(self, other) a.__iadd__(self, other)
減法 a-b a.__sub__(self, other) b.__rsub__(self, other) a.__isub__(self, other)
乘法 a*b a.__mul__(self, other) b.__rmul__(self, other) a.__imul__(self, other)
矩陣乘法 a@b a.__matmul__(self, other) b.__rmatmul__(self, other) a.__imatmul__(self, other)
除法 a/b a.__truediv__(self, other) b.__rtruediv__(self, other) a.__itruediv__(self, other)
帶餘除法求商 a//b a.__floordiv__(self, other) b.__rfloordiv__(self, other) a.__ifloordiv__(self, other)
求模 a%b a.__mod__(self, other) b.__rmod__(self, other) a.__imod__(self, other)
帶餘除法 divmod(a,b) a.__divmod__(self, other) b.__rdivmod__(self, other)
a**b
pow(a,b)
a.__pow__(self, other[, modulo]) b.__rpow__(self, other[, modulo]) a.__ipow__(self, other[, modulo])
按位左移 a<<b a.__lshift__(self, other) b.__rlshift__(self, other) a.__ilshift__(self, other)
按位右移 a>>b b.__rshift__(self, other) b.__rrshift__(self, other) a.__irshift__(self, other)
按位與 a&b a.__and__(self, other) b.__rand__(self, other) a.__iand__(self, other)
按位異或 a^b a.__xor__(self, other) b.__rxor__(self, other) a.__ixor__(self, other)
按位或 a|b a.__or__(self, other) b.__ror__(self, other) a.__ior__(self, other)

其中,特殊的:

冪運算[編輯 | 編輯原始碼]

函數pow(base, exp, mod=None)支持第三個數值[1]。所以如果要用__pow__方法來支持傳入三個參數的pow()函數,則__pow__方法應當支持可選的第三個參數。

由於強制轉換規則會太過複雜,三元版的pow()不會嘗試調用__rpow__

矩陣乘法[編輯 | 編輯原始碼]

Python 內置了矩陣乘法的運算符 @,但 Python 官方尚未提供矩陣乘法的相關實現。

特別備註[編輯 | 編輯原始碼]

通過特別設計__add__方法,我們可以讓加法不滿足交換律!事實上,讀者可以注意到,字符串的加法就是不可交換的。

在規定運算符時,一方面要注意運算符只是個符號:沒必要非得讓+對應加法,對應字符串的拼接也是可以的。另一方面要保證符號和其對應的運算易於記憶,不要讓+去對應一般認為的減法運算。

NotImplemented[編輯 | 編輯原始碼]

注意到二元運算的調用順序。我們考慮以下情況:

class mat():
    ...
    def __mul__(self, other):
        ...
        if isinstance(other, (int,float,complex)):
            #数量乘法
            ...
    def __rmul__(self, other):
        ...
        if isinstance(other, (int,float,complex)):
            return self.__mul__(other)
    ...

a = 3
m = mat(...)
am = a * m #作数量乘法

此時,Python 顯然會先嘗試調用 a.__mul__ 。而我們知道 int 類型是具有 __mul__ 方法的。為什麼這段代碼沒有報錯呢?

Python 應對這種方法時,添加了一個常量:NotImplemented。當某個方法,(例子中是 a.__mul__) 返回 NotImplemented 時,Python 就會像 a.__mul__ 不存在一樣,繼續嘗試調用下一個可能的方法(例子中是 m.__rmul__)。

對於讀者所自定義的類,在設計運算相關的方法時,應當保證「在與其它類的實例進行計算時,如果方法未涉及到與該類型實例的運算,則應當返回 NotImplemented」。

比較運算[編輯 | 編輯原始碼]

比較運算的方法調用和二元運算類似。但它沒有對調後的版本。

這些方法返回的內容不需要是布爾值。當它不返回布爾值時,如果比較運算的結果要被用於 if 語句或布爾運算等,Python 會對結果使用 bool() 函數,將其轉化為布爾值來確定結果為 True 還是 False

比較運算列表
名稱 運算符
函數
對應方法
小於 a<b a.__lt__(self, other)
小於等於 a<=b a.__le__(self, other)
大於 a>b a.__gt__(self, other)
大於等於 a>=b a.__ge__(self, other)
等於 a==b a.__eq__(self, other)
不等於 a!=b a.__ne__(self, other)

「等於」運算[編輯 | 編輯原始碼]

如果不定義該方法,默認該方法為:

True if self is other else NotImplemented

is / not is[編輯 | 編輯原始碼]

x is y 運算會比較兩個對象的 id 。簡單來講,這個運算的結果就是 id(x) == id(y) 的結果。

「不等於」運算[編輯 | 編輯原始碼]

如果不定義該方法,默認該方法為:

False if self.__eq__(other) != NotImplemented else NotImplemented

除「不等於」運算和「等於」運算在默認情況下有隱含關係外,其它比較運算在默認情況下沒有任何隱含關係。例如:x < y or x == y 為真,不代表 x <= y 為真。

functools.total_ordering[編輯 | 編輯原始碼]

該裝飾器放置在類聲明前,會自動為符合要求的類型補全比較運算的方法。具體來講,該類型必須包含:

  1. __eq__ 方法
  2. __lt__ / __le__ / __gt____ge__ 四者之一

它會按照易於理解的方式補全未定義的方法,使得「x < y or x == y 為真,等同於 x <= y 為真」。

具體來講,使用的大致方式如下:

from functools import total_ordering

@total_ordering
class ClassName:
    ...
    def __eq__(self, other):
        ...
    def __lt__(self, other):
        ...

比較運算與hash[編輯 | 編輯原始碼]

object.__hash__(self)[編輯 | 編輯原始碼]

該方法用於支持 hash() 函數。常見的方法是把實例的所有屬性打包成元組,並進行哈希操作,比如:

class Student:
    ...
    def __hash__(self):
        return hash( (self.Name,  self.Gender, self.ID) )

擁有哈希操作的類型不應是可變類型。

如果一個類沒有定義 __eq__ 方法,則它也不應定義 __hash__ 操作;如果它定義了 __eq__ 方法但沒有定義 __hash__ 方法,則其實例將不可被用作可哈希多項集的條目(比如 set, frozenset 的元素,以及 dict 的鍵)。

讀者定義的類會默認帶有 __eq__ 和 __hash__ 方法。如果在這兩個方法都使用默認,則 __hash__ 方法會返回一個恰當的值,使得 x == y 同時意味着 x is y 和 hash(x) == hash(y)。

一個類如果自定義了 __eq__ 方法而沒有定義 __hash__ 方法,則其默認的 __hash__ 方法會被自動更改為 None 變量。

參考文獻[編輯 | 編輯原始碼]