python运算符重载

Posted Sakura

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python运算符重载相关的知识,希望对你有一定的参考价值。

python对运算符重载的一些限制

1.不能重载内置类型的运算符

2.不能新建运算符,只能重载现有的

3.某些运算符不能重载:is、and、or、not

建立某Vector类

 1 from array import array
 2 import reprlib
 3 import math
 4 
 5 
 6 class Vector:
 7     typecode = \'d\'
 8     shortcut_names = \'xyzt\'
 9 
10     def __init__(self, components):
11         self._components = array(self.typecode, components)
12 
13     def __iter__(self):
14         return iter(self._components)
15 
16     def __repr__(self):
17         components = reprlib.repr(self._components)
18         components = components[components.find(\'[\'):-1]
19         return \'Vector({})\'.format(components)
20 
21     def __str__(self):
22         return str(tuple(self))
23 
24     def __bytes__(self):
25         return (bytes([ord(self.typecode)]) + bytes(self._components))
26 
27     def __eq__(self, other):
28         return tuple(self) == tuple(other)
29 
30     def __abs__(self):
31         return math.sqrt(sum(x * x for x in self))
32 
33     def __bool__(self):
34         return bool(abs(self))
35 
36     @classmethod
37     def frombytes(cls, octets):
38         typecode = chr(octets[0])
39         memv = memoryview(octets[1:]).cast(typecode)
40         return cls(memv)
Vector

可见Vector新Vector

重载一元运算符

-  (__neg__)         一元取负运算符

+  (__pos__)         一元取正运算符

~  (__invert__)      对整数按位取反

     (__abs__)              取绝对值

实现它们:

    def __neg__(self):                     
        return Vector(-x for x in self)      #创建一个新的Vector实例,把self的每个分量都取反
    
    def __pos__(self):
        return Vector(self)                  #创建一个新的Vector实例,传入self各个分量

__invert__不实现是因为,计算~v时python会抛出TypeError,且输出详细的错误信息,这符合预期。

!!注意:x和+x可能不相等,例如

import decimal
ctx = decimal.getcontext()      #获取当前全局算术运算的上下文引用
ctx.prec = 40                   #把算术运算上下文精度设置为40
one_third = decimal.Decimal(\'1\') / decimal.Decimal(\'3\')  #计算1/3
print(one_third)
print(one_third == +one_third)  #
ctx.prec = 28                   #把精度降低为28,默认精度
print(one_third == +one_third)
print(+one_third)


0.3333333333333333333333333333333333333333
True 
False                                       #one_third == +one_third返回False
0.3333333333333333333333333333              #小数点后位是28位而不是40位

重载 + 运算符 

两个向量加在一起生成一个新向量,如果两个不同长度的Vector实例加一起呢,可以抛出错误,但最好是用0填充,那么可以:

    def __add__(self, other):
        pairs = itertools.zip_longest(self, other, fillvalue=0)
        return Vector(a + b for a, b in pairs)
        #pairs是一个生成器,生成(a, b)形式元组,a来自self,b来自other,0填充

尝试调用:

if __name__ == \'__main__\':
    V1 = Vector([3, 4, 5])
    V2 = V1 + (2, 3, 4, 5)
    print(repr(V2))
    V3 = (2, 3, 4, 5) + V1
    print(repr(V3))

#结果
Vector([5.0, 7.0, 9.0, 5.0])
TypeError: can only concatenate tuple (not "Vector") to tuple

实现的加法可以处理任何数值可迭代对象,但是对调操作数加法就会失败。实际上a + b调用的是a.__add__(b),而b + a自然是调用b.__add__(a)。而(2,3,4,5)显然没有实现这样的加法,如何解决?

为了支持涉及不同类型的运算,python为中缀运算符提供了特殊的分派机制。对于表达式a + b来说,解释器会执行以下几步操作:

(1)如果a有__add__方法,而且返回值不是NotImplemented,调用a.__add__(b),然后返回结果。

(2)如果a没有__add__方法,或者__add__方法返回值是NotImplemented,检查b有没有__radd__方法,如果有而且没有返回NotImplemented,调用b.__radd__(a),然后返回结果。

(3)如果b没有__radd__方法,或者__radd__方法返回值是NotImplemented,抛出TypeError,并在错误消息中指明操作类型不支持。

__radd__是__add__的反向版本。那么加法可以这样写:

    def __add__(self, other):
        pairs = itertools.zip_longest(self, other, fillvalue=0)
        return Vector(a + b for a, b in pairs)
    
    def __radd__(self, other):
        return self + other        #__radd__直接委托__add__

新问题是,如果操作类型是单个数或者‘helloworld’这样的字符串抛出的错误不合理:

    V1 = Vector([3, 4, 5])
    V2 = V1 + 1
#TypeError: zip_longest argument #2 must support iteration

    V1 = Vector([3, 4, 5])
    V2 = V1 + \'abc\'
#TypeError: unsupported operand type(s) for +: \'float\' and \'str\'

由于类型不兼容而导致运算符特殊方法无法返回有效的结果,那么应该返回NotImplemented,而不是TypeError。返回NotImplemented时,另一个操作数所属的类型还有机会执行运算,即python会尝试调用反向方法。

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

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

发生TypeError时,解释器尝试调用反向运算符方法,如果操作数是不同的类型,对调之后可能反向运算符可以正确计算。

重载 * 运算符 

向量 * x,如果x是数值,就是向量每个分量乘以x。

    def __mul__(self, other):
        return Vector(n * other for n in self)
    
    def __rmul__(self, other):
        return self * other

问题是:提供不兼容的操作数就会出问题,other需要的参数是数字,传入bool类型,或者fractions.Fraction实例等就会出问题。

 这里采用类型检测,但是不硬编码具体类型,而是检查numbers.Real(因为不可能是复数)抽象基类,这个抽象基类涵盖了所有可能可以参与这个运算的类型

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

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

中缀运算符见表:

重载比较运算符

比较运算符区别于+ * / 等:

(1) 正向反向调用使用的是同一系列方法,规则如表13-2.例如__gt__方法调用反向__lt__方法,并且把参数对调。

(2)对于==和!=来说,如果反向调用失败,python会比较对象ID,而不抛出TypeError。

仅仅比较长度以及各个分量显然是不合理的,因该加上类型检查:

    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__返回的结果取反

(事实上可能x==y成立不代表x!=y不成立,有时需要定义__ne__方法)

 object继承的__ne__方法即是下面代码的C语言版本:

    def __ne__(self, other):
        eq_result = self == other
        if eq_result is NotImplemented:
            return NotImplemented
        else:
            return not eq_result

重载增量赋值运算符

 对于Vector类,它是不可变类型,是不能实现 += 这样的就地方法的。

某可变的类型:

class B(A):

def __add__(self, other):
    if isinstance(other, A):
        return B(self.某属性+other.某属性)
    else:
        return NotImplemented

def __iadd__(self, other):
    if isinstance(other, A)
        other_iterable = other.某属性
    else:
        try:
            other_iterable = iter(other)
        except TpyeError:
             #抛出xxx错误
        使用other_iterable更新self
        return self

 以上来自《流畅的python》

以上是关于python运算符重载的主要内容,如果未能解决你的问题,请参考以下文章

Python——运算符重载

Python面向对象运算符重载

7Python全栈之路系列之面向对象运算符重载

Python 3 之 运算符重载详解

python运算符重载

Python面向对象的运算符重载