运算符重载基础

  • 不能重载内置类型的运算符
  • 不能新建运算符,只能重载现有的
  • 某些运算符不能重载 例如 is, and, or 和not(不过位运算符&、|和~可以)

一元运算符

  • -(neg):一元取负算术运算符。如果x 是-2,那么-x == 2。
  • +(pos):一元取正算术运算符。通常,x ==+x
  • ~(invert)对整数按位取反,定义为~x == -(x+1)。如果x 是2,那么~x == -3

支持一元运算符很简单,只需实现相应的特殊方法。这些特殊方法只有一个参数,self。然后,使用符合所在类的逻辑实现。不过,要遵守运算符的一个基本规则:始终返回一个新对象。也就是说,不能修改self,要创建并返回合适类型的新实例

# 在vector.py代码中添加如下代码支持一元运算符
def __neg__(self):
    return Vector(-x for x in self)

def __pos__(self):
    return Vector(self)

重载加法运算符

目标结果

>>> v1 = Vector([3, 4, 5])
>>> v2 = Vector([6, 7, 8])
>>> v1 + v2
Vector([9.0, 11.0, 13.0])
>>> v1 + v2 == Vector([3+6, 4+7, 5+8])
True
# 两个不同长度的向量相加
>>> v1 = Vector([3, 4, 5, 6])
>>> v3 = Vector([1, 2])
>>> v1 + v3
Vector([4.0, 6.0, 5.0, 6.0])

特殊方法实现

# 在Vector类中定义
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:
        """
        如果中缀运算符方法抛出异常,就终止了运算符分派机制。
        对TypeError 来说,通常最好将其捕获,然后返回NotImplemented。
        这样,解释器会尝试调用反向运算符方法,如果操作数是不同的类型,
        对调之后,11反向运算符方法可能会正确计算。
        """
        return NotImplemented
        
	"""为了让混合类型加法能正确计算,我们要实现Vector.__radd__方法。
	这是一种后备机制,如果左操作数没有实现__add__ 方法,或者实现了,
	但是返回NotImplemented 表明它不知道如何处理右操作数,那么Python 会
	调用__radd__ 方法
	"""
	def __radd__(self, other):
	    return self + other

重载标量乘法运算符

class Vector:
    type code = 'd'
    def __init__(self, components):
        self._components = array(self.typecode, components)
        
    def __mul__(self, scalar):
        """如果scalar 是numbers.Real 某个子类的实例,
        用分量的乘积创建一个新Vector 实例"""
        if isinstance(scalar, numbers.Real): 
            return Vector(n * scalar for n in self)
        # 否则,返回NotImplemented,让Python 尝试在scalar 操作数上调用__rmul__ 方法
        else: 
            return NotImplemented
            
    # 这里,__rmul__ 方法只需执行self * scalar,委托给__mul__ 方法
    def __rmul__(self, scalar):
        return self * scalar

python3.5中还引入了点积运算符@

class Vector:
    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
        
>>> va = Vector([1, 2, 3])
>>> vz = Vector([5, 6, 7])
>>> va @ vz == 38.0 # 1*5 + 2*6 + 3*7
True
>>> [10, 20, 30] @ vz
380.0
>>> va @ 3
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for @: 'Vector' and 'int'

比较运算符

常见的比较运算符有(==, !=, >, <, >=, <=)

中缀运算符

正方向调用

反方向调用

后备机制

a==b

a.__eq__(b)

b.__eq__(a)

返回id(a) == id(b)

a!=b

a.__ne__(b)

b.__ne__(a)

返回not(a==b)

a>b

a.__gt__(b)

b.__lt__(a)

抛出TypeError

a<b

a.__lt__(b)

b.__gt__(a)

抛出TypeError

a>=b

a.__ge__(b)

b.__le__(a)

抛出TypeError

a<=b

a.__le__(b)

b.__ge__(a)

抛出TypeError

看看Vector类中的__eq__方法和__ne__方法

def __eq__(self, other):
    if isinstance(other, Vector): 
        return (len(self) == len(other) and all(a == b for a, b in zip(self, other)))
    else:
        return NotImplemented

"""那么!= 运算符呢?我们不用实现它,因为从object 继承的__ne__ 方法的
后备行为满足了我们的需求:定义了__eq__ 方法,而且它不返回NotImplemented,
__ne__ 会对__eq__ 返回的结果取反。
"""
def __ne__(self, other):
    eq_result = self == other
    if eq_result is NotImplemented:
        return NotImplemented
    else:
        return not eq_result

对于Python3而言,定义了__eq__方法, 就不需要再定义__ne__方法, 因为从object继承的__ne__方法足够用了,几乎不用重载

增量赋值运算符

增量赋值不会修改不可变目标,而是新建实例,然后重新绑定

>>> v1 = Vector([1, 2, 3])
>>> v1_alias = v1 
>>> id(v1) 
4302860128
# 增量加法创建了心得实例
>>> v1 += Vector([4, 5, 6]) 
>>> v1 
Vector([5.0, 7.0, 9.0])
>>> id(v1) 
4302859904
# v1_alias没被修改
>>> v1_alias 
Vector([1.0, 2.0, 3.0])

# 运算与预期相符,但创建了新的Vector实例
>>> v1 *= 11 #
>>> v1 
Vector([55.0, 77.0, 99.0])
>>> id(v1)
4302858336
  • 增量赋值运算符只是语法糖:a += b 的作用与a = a + b 完全一样。对不可变类型来说,这是预期的行为,而且,如果定义了__add__ 方法的话,不用编写额外的代码,+= 就能使用
  • 然而,如果实现了就地运算符方法,例如__iadd__,计算a += b 的结果时会调用就地运算
    符方法。这种运算符的名称表明,它们会就地修改左操作数,而不会创建新对象作为结果。
  • 不可变类型,如Vector 类,一定不能实现就地特殊方法

我们发现,Python吃力运算符的方式是把它们当常规的运算符加上赋值操作,即a+=b, 其实会当成a = a + b 处理。这样会始终创建新对象,因此可变类型和不可变类型都能用。对于可变对象来说,可以实现就地特殊方法, 例如支持+=的__iadd__方法,然后修改左操作数的值