使用者: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。
我們將詳細講解加法運算,然後直接類推到其它種類的運算。
我們有三個方法與加法運算有關。
此方法對應 self + other 的情況。該方法返回的值應當為程序員希望 self + other 所得到的結果。
此方法對應 other + self 的情況。該方法返回的值應當為程序員希望 other + self 所得到的結果。
此方法對應 self += other 的情況,返回對 self 進行賦值後的結果。
在方法運行時,該方法可以完全不對 self 進行修改。Python 會在方法結束後,自動把該方法的返回值賦值給 self。
對於 a + b 運算時,會先嘗試調用 a.__add__ 方法。如果調用失敗,且 a 與 b 的類型不相同時,則會嘗試調用 b.__radd__。
而對於 a += b 運算時,會先嘗試調用 a.__iadd__ 方法。如果調用失敗,就改為計算 a = a + b,並按照上面規則嘗試調用 a.__add__ 或 b.__radd__。
由於上述規則,如果要讓自製類型與其它類型自由地進行左加和右加運算,只需要定義自製類型的 __add__ 和 __radd__ 就可以了。
其它二元運算和加法完全是類似的。我們將這些運算總結到以下列表裡。
| 名稱 | 運算符 函數 |
對應方法 | 對應方法(右) | 賦值運算 |
|---|---|---|---|---|
| 加法 | 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**bpow(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__方法,我們可以讓加法不滿足交換律!事實上,讀者可以注意到,字符串的加法就是不可交換的。
在規定運算符時,一方面要注意運算符只是個符號:沒必要非得讓+對應加法,對應字符串的拼接也是可以的。另一方面要保證符號和其對應的運算易於記憶,不要讓+去對應一般認為的減法運算。
在 Python 中,進行布爾運算時,自始至終都只會調用對象的 __bool__ 方法。之前章節講述的布爾運算規則,實際上是一個「使用於 if/while 等判斷語句中時」的特例。Python 實際的布爾運算規則如下:
| 布爾運算 | 規則 |
|---|---|
| A and B | 如果 bool(A) 返回 True,則運算結果為 B;否則運算結果為 A。 |
| A or B | 如果 bool(A) 返回 True,則運算結果為 A;否則運算結果為 B。 |
| not A | 如果 bool(A) 返回 True,則運算結果為 False;否則運算結果為 True。 |
除了 not 運算必然返回布爾值之外,and 運算和 or 運算返回的內容不一定是布爾值。讀者是不能通過修改運算規則來決定布爾運算的結果的。讀者只能通過定義與bool() 函數相關的 __bool__ 方法或 __len__ 方法來自定義布爾運算的結果。
注意到二元運算的調用順序。我們考慮以下情況:
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
一些對象(比如第三方模塊 numpy 的 ndarray 對象)會自定義 == 所代表的運算,所以在不需要模塊給出的自定義等於運算時,可以改為用 is 運算,is 運算不會被自定義。
我們之前介紹過is運算。實際上 x is y 運算會比較兩個對象的 id,如果 id(x) 和 id(y) 相同,則 x is y 返回 True。
如果不定義該方法,默認該方法為:
False if self.__eq__(other) != NotImplemented else NotImplemented
除「不等於」運算和「等於」運算在默認情況下有隱含關係外,其它比較運算在默認情況下沒有任何隱含關係。例如:x < y or x == y 為真,不代表 x <= y 為真。
該裝飾器放置在類聲明前,會自動為符合要求的類型補全比較運算的方法。具體來講,該類型必須包含:
__eq__方法__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() 函數。常見的方法是把實例的所有屬性打包成元組,並進行哈希操作,比如:
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 變量。