用户: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 变量。