运算符重载

Python 关于运算符重载的规则:

  • 不能重载内置类型的运算符
  • 不能新建,只能重载
  • 某些运算符不能重载--is、and、or 和 not

一元运算符

  • __neg__
  • __pos__
  • __invert__

一元运算符是返回新建对象,不能修改self.

加法+

在 Vector 中__add__ 定义我觉得很厉害:



def __add__(self, other):
    try:
        pairs = itertools.zip_longest(self, other, fillvalue=0.0)
        return Vector(a + b for a, b in pairs)
    except TypeError:
        return NotImplemented

def __radd__(self, other):
    return self + other



之所以要采取这种方式定义有以下好处:

  • Vector 可以和任意长度的 iterable 类型相加
  • 返回一个新的 Vector, 不影响 self 或 other

而我们用 NotImplemented 这样更好是因为整个运算的顺序是如下图:





这样我们能最大化得到我们的结果。还需注意: NotImplemented 并不是 NotImplementedError, NotImplemented 是特殊的单例值,而 NotImplementedError 则是异常。

除了__add__ 以外,我们最好还用上 __radd__(反向加法,即交换操作数的加法)。

乘法*

Vector 对于乘法的重载也是类似的:


def __mul__(self, scalar):
    if isinstance(scalar, numbers.Real):
        return Vector(n * scalar for n in self)
    else:
        return NotImplemented

def __rmul__(self, scalar):
    return self * scalar


注意这里我们值定义了向量乘以一个数的乘法。作者也尝试给我们做点乘,我也才知道矩阵乘法被分配给了@符号,我们也可以做这个定义,然后来运算


def __matmul__(self, other):
    try:
        return sum(a * b for a, b in zip(self, other))
    except TypeError:
        return NotImplemented

def __rmatmul__(self, other):
    return self @ other  # this only works in Python 3.5


比较运算

甚至我发现比较运算都和加法的调用过程类似,当我们比较 Vector 和 Vector2d 的时候,虽然第二个操作数不是 Vector,得到了 NotImplemented,然后我们又调用 Vector2d 的比较操作,此时两个被变成元组,然后就得到了相等的结果。

增量赋值运算

如果我们没有添加 __iadd__, 我们当然也能使用 a += b,但这里是语法糖,相当于 a = a + b ,如果我们考察 a 的 id,还是会发现它已经变了。如果我们实现了__iadd__,当然我们实现的时候就应当就地修改左操作数,而不生成新的对象。

当然对于不可变类型,就一定不能实现这种方法。


import itertools  # 先导入标准库模块

from tombola import Tombola
from bingo import BingoCage


class AddableBingoCage(BingoCage):  # 扩展自BingoCage

    def __add__(self, other):
        if isinstance(other, Tombola):  # add操作数第二个只能是 Tombola
            return AddableBingoCage(self.inspect() + other.inspect())  
        else:
            return NotImplemented

    def __iadd__(self, other):
        if isinstance(other, Tombola):
            other_iterable = other.inspect()  
        else:
            try:
                other_iterable = iter(other)  #如果是别的类,用other创造迭代器
            except TypeError:  # 抛出异常
                self_cls = type(self).__name__
                msg = "right operand in += must be {!r} or an iterable"
                raise TypeError(msg.format(self_cls))
        self.load(other_iterable)  # 重载入 self
        return self  # 增量赋值特殊方法必须返回self


这个例子告诉了我们几点:

  • iadd 比 add 更宽容
  • 一般如果中缀运算符的正向方法的other与self属于同类型,则没必要定义反向方法,反向更多的是为了处理类型不同的操作数