Python基础之正确重载运算符

导语:本文章记录了本人在学习Python基础之面向对象篇的重点知识及我的心得,打算入门Python的朋友们能够来一块儿学习并交流。

本文重点:python

一、掌握运算符重载的定义和做用,以及Python对其的内部限制;
二、掌握一元运算符重载设计思路;
三、理解中缀运算符重载过程当中鸭子类型和白鹅类型思想的运用并掌握。

1、运算符重载基础

运算符重载:对已有的运算符进行从新定义,赋予其另外一种功能,以适应不一样的数据类型。
重载的做用:令用户定义的对象可以使用中缀运算符(如 + 和 | )或一元运算符(如 - 和 ~ )等运算符。
为了作好灵活性、可用性和安全性方面的平衡,Python对运算符重载施加了一些限制:安全

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

2、一元运算符

一、常见的一元运算符

  • -(__neg__),一元取负算术运算符。例:若 x 是 -2,则 -x==2。
  • +(__pos__),一元取正算术运算符。一般x==+x。
  • ~(__invert__),对整数按位取反,~x== -(x+1)。例:若 x 是 -2,则 ~x==1。

另外,Python语言参考手册将内置的abs()函数列为一元运算符,它对应的特殊方法是__abs__。函数

二、重载一元运算符

重载一元运算符只需实现相应的特殊方法,这些特殊方法只有self一个参数。
重载应遵循运算符的一个基本规则:始终返回一个新对象
即,不能修改self,要建立并返回合适类型的新实例。学习

下面咱们以第10章的多维向量类为例重载一元运算符:spa

import math
class Vector:
#排版须要省略中间代码
    def __abs__(self):
        return math.sqrt(sum(x*x for x in self))
    
    def __neg__(self):
        return Vector(-x for x in self)
    
    def __pos__(self):
        return Vector(self)

    def __invert__(self):
        return Vector(-x-1 for x in self)

三、x和+x不相等的状况

  • 算术运算上下文的精度变化可能致使 x 不等于 +x

图片描述
Python 3.4 为 Decimal 算术运算设定的默认精度是28,这里由于+x使用上下文的精度致使相等性判断返回False。设计

  • counter实例不含零值和负值计算器

图片描述
经过上面的实例可以看到counter实例ct通过零值和负值的赋值以后,再通过+x运算后发现ct实例中的非负数对象均消失了。事实上一元运算符 + 等同于加上一个空 Counter。当Counter相加时,Python解释器从实用性角度出发会把负值和零值的计数从结果中剔除。code

3、中缀运算符

一、重载加法__add__

如今咱们仍以第10章的多维向量为例进行中缀运算符加号“+”的重载。
重载加法的目标分析orm

  • 当多维向量类是操做数时,多维向量应支持与同类向量的加法
  • 同时多维向量类还应支持与可迭代对象的加法
  • 此外当可迭代对象是操做数的时候,多维对象应具有__radd__如此来调用多维向量类中的__add__方法

重载加法的流程图设计:
设计的重点在于采用鸭子类型思想。当多维向量类与非数值类相加时,多维向量类没法处理异类加法运算能够将加法运算交给右操做数的类处理。由于右操做数存在能够处理这种异类加法的可能。
图片描述对象

重载加法的代码实现:继承

from itertools import zip_longest
class Vector:
#排版须要省略中间代码
    def __add__(self, other):
        try:
            return Vector(a+b for a,b in zip_longest(self,other,fillvalue=0))
        except TypeError:
            return NotImplemented
    def __radd__(self, other):
        return self+other

二、重载乘法__mul__
重载加法的目标分析

  • 当多维向量类是操做数时,多维向量应支持与同类向量的乘法
  • 同时多维向量类还应支持与可迭代对象的加法
  • 此外当可迭代对象是操做数的时候,多维对象应具有__rmul__如此来调用多维向量类中的__mul__方法

注意:咱们对多维向量重载的乘法是针对数论中的实数类型进行运算,此时能够采用白鹅类型显式检查对象的抽象基类是否为numbers.Real,代码实现以下:

import numbers 
class Vector:
#排版须要省略中间代码
    def __mul__(self, other):
        if isinstance(other,numbers.Real):
                return Vector(x*other for x in self)
        else:
            return NotImplemented
    def __rmul__(self, other):
        return self*other

Tips:通常来讲只有当处理与self不一样类型的操做数时,须要建立反向方法处理。不然没有必要建立反向方法。

4、比较运算符

Python 解释器对众多比较运算符(==、 !=、 >、 <、 >=、 <=) 的处理与前文相似, 不过在两个方面有重大区别。

  • 正向和反向调用使用的是同一系列方法。这方面的规则以下表所示。例如,对 == 来讲,正向和反向调用都是 _eq_ 方法,只是把参数对调了;而正向的 _gt_ 方法调用的是反向的 __lt__方法, 并把参数对调。
  • 对 == 和 != 来讲,若是反向调用失败,Python 会比较对象的 ID,而不抛出 TypeError。

图片描述

一、重载等号__eq__

如今咱们仍以第10章的多维向量为例进行中缀运算符等号“=”的重载。
重载等号的返回为True的条件

  • 等号两端对象为同类对象
  • 等号两端对象中的每一个元素都必须对应相等

注意:若Vector处理等号不为True,应该返回NotImplemented交由Python处理。若是反向调用返回 NotImplemented,Python 会使用后备机制比较对象的 ID,做最后一搏。

重载等号的代码实现以下:

class Vector:
#排版须要省略中间代码
    def __eq__(self, other):
        if isinstance(other,Vector):
            return len(self)==len(other) and all(x==y for x,y in zip(self,other))
        else:
            return NotImplemented

二、了解object中__ne__的实现

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

5、增量赋值运算符

一、重载加等于__iadd__

如今咱们仍以第10章的多维向量为例进行中缀运算符加等于“+=”的重载。
重载加等于设计要求

  • 加等于右侧的对象与左侧的Vector类是同类对象或可迭代对象
  • 不然抛出TypeError,显示没法进行加等于计算

下面以BingoCage的子类AddableBingoCage为例实现__iadd__,你们没必要在乎这个子类,重点在于理解__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()) 
            #self.inspect()继承自BingoCage,返回当前元素组成的有序元组。
        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
相关文章
相关标签/搜索