资讯专栏INFORMATION COLUMN

Python基础之正确重载运算符

Scliang / 2294人阅读

摘要:一运算符重载基础运算符重载对已有的运算符进行重新定义,赋予其另一种功能,以适应不同的数据类型。重载一元运算符重载一元运算符只需实现相应的特殊方法,这些特殊方法只有一个参数。

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

本文重点:

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

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

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

能新建运算符,只能重载现有运算符

某些运算符不能重载,如is、and、or和not(不过位运算符&、| 和 ~可以)

二、一元运算符 1、常见的一元运算符

-(__neg__),一元取负算术运算符。例:若 x 是 -2,则 -x==2。

+(__pos__),一元取正算术运算符。通常x==+x。

~(__invert__),对整数按位取反,~x== -(x+1)。例:若 x 是 -2,则 ~x==1。

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

2、重载一元运算符

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

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

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)
3、x和+x不相等的情况

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


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

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


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

三、中缀运算符 1、重载加法__add__

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

当多维向量类是操作数时,多维向量应支持与同类向量的加法

同时多维向量类还应支持与可迭代对象的加法

此外当可迭代对象是操作数的时候,多维对象应具备__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

2、重载乘法__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不同类型的操作数时,需要创建反向方法处理。否则没有必要创建反向方法。

四、比较运算符

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

正向和反向调用使用的是同一系列方法。这方面的规则如下表所示。例如,对 == 来说,正向和反向调用都是 _eq_ 方法,只是把参数对调了;而正向的 _gt_ 方法调用的是反向的 __lt__方法, 并把参数对调。

对 == 和 != 来说,如果反向调用失败,Python 会比较对象的 ID,而不抛出 TypeError。

1、重载等号__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
2、了解object中__ne__的实现
def __ne__(self, other):
    eq_result = self == other
    if eq_result is NotImplemented:
        return NotImplemented
    else:
        return not eq_result
五、增量赋值运算符 1、重载加等于__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 

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44565.html

相关文章

  • Python入门学习笔记汇总

    摘要:导语本文章汇总了本人在学习基础之绪论篇数据结构篇函数篇面向对象篇控制流程篇和元编程篇学习笔记的链接,打算入门的朋友们可以按需查看并交流。 导语:本文章汇总了本人在学习Python基础之绪论篇、数据结构篇、函数篇、面向对象篇、控制流程篇和元编程篇学习笔记的链接,打算入门Python的朋友们可以按需查看并交流。 第一部分:绪论篇 1、Python数据模型 第二部分:数据结构篇 2、序列构成...

    U2FsdGVkX1x 评论0 收藏0
  • Python学习路20-数据模型

    摘要:前言数据模型其实是对框架的描述,它规范了这门语言自身构件模块的接口,这些模块包括但不限于序列迭代器函数类和上下文管理器。上述类实现了方法,它可用于需要布尔值的上下文中等。但多亏了它是特殊方法,我们也可以把用于自定义数据类型。 《流畅的Python》笔记。本篇是Python进阶篇的开始。本篇主要是对Python特殊方法的概述。 1. 前言 数据模型其实是对Python框架的描述,它规范了...

    ad6623 评论0 收藏0
  • [零基础python]模块的加载

    摘要:找到模块文件之后,将其编译成字节码,就是那个文件里面的关于字节码,下面会介绍,请继续阅读。当然,如果根本就没有找到同名的源文件,只有字节码文件,那么就只能运行这个了。执行就是前面已经编译的模块字节码文件,顺理成章要执行了。 不管是用import还是用from mmmm import *的方式导入模块,当程序运行之后,回头在看那个存储着mmmm.py文件的目录中(关于mmmm.py文件可...

    tinylcy 评论0 收藏0
  • Python数据模型

    摘要:本文重点了解数据模型和接口的概念掌握特殊方法的定义,作用和基本用法。一基本概念数据模型是数据特征的抽象,这里是对框架的描述。数据模型规范了自身构建模块的接口,模块包括但不限于序列迭代器函数类和上下文管理器。 导语:本文章记录了本人在学习Python基础之绪论篇的重点知识及个人心得,以加深自己的理解。 本文重点: 1、了解Python数据模型和接口的概念;2、掌握特殊方法的定义,作用和基...

    Flink_China 评论0 收藏0
  • [零基础python]重回函数

    摘要:函数的基本结构中的函数基本结构函数名参数列表语句几点说明函数名的命名规则要符合中的命名要求。在中,将这种依赖关系,称之为多态。不要期待在原处修改的函数会返回结果比如一定要之用括号调用函数不要在导入和重载中使用扩展名或路径。 在本教程的开始部分,就已经引入了函数的概念:《永远强大的函数》,之所以那时候就提到函数,是因为我觉得函数之重要,远远超过一般。这里,重回函数,一是复习,二是要在已经...

    dmlllll 评论0 收藏0

发表评论

0条评论

Scliang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<