用戶: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**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__
方法,我們可以讓加法不滿足交換律!事實上,讀者可以注意到,字符串的加法就是不可交換的。
在規定運算符時,一方面要注意運算符只是個符號:沒必要非得讓+
對應加法,對應字符串的拼接也是可以的。另一方面要保證符號和其對應的運算易於記憶,不要讓+
去對應一般認為的減法運算。
在 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
x is y
運算會比較兩個對象的 id
。簡單來講,這個運算的結果就是 id(x) == id(y)
的結果。
如果不定義該方法,默認該方法為:
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 變量。