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

參考文獻[编辑 | 编辑源代码]