FluentPythonCh13

14th December 2017 at 4:46pm
Fluent Python

操作符重载,也是通过一些 dundle 函数来实现的。比如 __add__, __radd__ 等。操作符分为一元(unary)和二元(binary / infix),重载函数长得像:

class Vector:
    def __neg__(self):
        return Vector(-x for x in self)

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

二元操作符,有正向反向之分,比如对于 +,有:

  • Vector([1, 2]) + [3]:调用了 Vector.__add__ 函数
  • [3] + Vector([1, 2]):调用 Vector.__radd__ 函数

关于 a+b 会怎么执行,这个图描述得很清楚:

同时二元操作符都有 in-place 版本,比如 __iadd__,即 += 操作符对应的函数。

写操作符重载函数很 trivial,需要写时具体看看书中建议。比较值得关注的是,如果二元操作符的两个值并不适合做操作(比如 'str' + [1, 2, 3]),你的相应 __add__ 函数需要返回 NotImplemented 常量(而不是抛出 NotImplementedError 异常)。

对于用于比较的操作符,他们不需要专门的 reverse method,如下图:

如何写 __add____iadd__,书中有个不错的例子:

import itertools   
from tombola import Tombola
from bingo import BingoCage


class AddableBingoCage(BingoCage):   
    def __add__(self, other):
        if isinstance(other, 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)   
            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)
        return self

作者总结了下:

  • __add__: The result is produced by calling the constructor AddableBingoCage to build a new instance.
  • __iadd__: The result is produced by returning self, after it has been modified.

Future Reading 和 Soapbox 部分,大多数内容涉及到不同语言对于操作符重载的选择和讨论。比如 Java 和 Go 就不支持操作符重载。Java 作者觉得 C++ 里操作符重载被滥用了,写了一堆不可理解的(not sensible)代码出来,它宁愿牺牲掉这块特性。