首页 > 科技 > 长文---Python运算符重载

长文---Python运算符重载

“运算符重载”只是意味着在类方法中拦截内置的操作——当类的实例出现在内置操作中,Python自动调用你的方法,并且你的方法的返回值变成了相应操作的结果。以下是对重载的关键概念的复习:

·运算符重载让类拦截常规的Python运算。

·类可重载所有Python表达式运算符。

·类也可重载打印、函数调用、属性点号运算等内置运算。

·重载使类实例的行为像内置类型。

·重载是通过提供特殊名称的类方法来实现的。

换句话说,当类中提供了某个特殊名称的方法,在该类的实例出现在它们相关的表达式时,Python自动调用它们。正如我们已经学过的,运算符重载方法并非必需的,并且通常也不是默认的;如果你没有编写或继承一个运算符重载方法,只是意味着你的类不会支持相应的操作。然而,当使用的时候,这些方法允许类模拟内置对象的接口,因此表现得更一致。

常见的运算符重载方法



注意:所有重载方法的名称前后都有两个下划线字符,以便把同类中定义的变量名区别开来。特殊方法名称和表达式或运算的映射关系,是由Python语言预先定义好的(在标准语言手册中有说明)。例如,名称__add__按照Python语言的定义,无论__add__方法的代码实际在做些什么,总是对应到了表达式+。

在Python中,如果我们想实现创建类似于序列和映射的类(可以迭代以及通过[下标]返回元素),可以通过重写__getitem__、__setitem__、__delitem__、__len__方法去模拟。

__getitem__(self,key):返回键对应的值。
__setitem__(self,key,value):设置给定键的值
__delitem__(self,key):删除给定键对应的元素。
__len__():返回元素的数量

只要实现了__getitem__和 __len__方法,就会被认为是序列。而序列则可以迭代,比如用于for循环。

下面来看一下__getitem__的基本用法:

这里出现了slice对象,slice() 函数实现切片对象,主要用在切片操作函数里的参数传递。

参数说明:

  • start -- 起始位置
  • stop -- 结束位置
  • step -- 间距

例:

a_slice=slice(2,6,2) #从2 开始,到6 结束(不包含在内),每隔2取数

data=[1,2,3,4,5,6,7,8,9]

print(data[a_slice])

输出:[3,5]

__setitem__的使用方法类似于__getitem__,起作用是通过切皮的方法设置索引值:


__getitem__的索引迭代:

如果定义了此方法,for循环每次都会调用这个方法,从而产生更高的偏移值,甚至可以自定义偏移值递增方法:


自定义迭代器

为了更好理解,先介绍一组基本概念:

  • Iterable: 有迭代能力的对象,一个类,实现了__iter__,那么就认为它有迭代能力,通常此函数必须返回一个实现了__next__的对象,如果自己实现了,你可以返回self,当然这个返回值不是必须的;
  • Iterator: 迭代器(当然也是Iterable),同时实现了__iter__和__next__的对象,缺少任何一个都不算是Iterator。
  • 可以使用collection.abs里面的Iterator和Iterable配合isinstance函数来判断一个对象是否是可迭代的,是否是迭代器对象
  • 只要实现了__iter__的对象就是可迭代对象(Iterable),正常情况下,应该返回一个实现了__next__的对象(虽然这个要求不强制),如果自己实现了__next__,当然也可以返回自己
  • 同时实现了__iter__和__next__的是迭代器(Iterator),当然也是一个可迭代对象了,其中__next__应该在迭代完成后,抛出一个StopIteration异常
  • for语句会自动处理这个StopIteration异常以便结束for循环

例:生成一组平方数


有多个迭代器的对象:要达到多个迭代器的效果,__it的er__只需要定义新的状态对象,而不是返回self。


属性引用:__getattr__与__setattr__

正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。要避免这个错误,除了可以加上一个属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。

class student:    def __init__(self,name):        self.name=namest=student('shun')st.name'shun'st.ageAttributeError: 'student' object has no attribute 'age'    #报错

这时我们尝试重写属性:

class student:    def __init__(self,name):        self.name=name    def __getattr__(self,attr):        if attr=='age':            return 20        else :            raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)st=student('shun')st.name'shun'st.age20

__setattr__(self, item, value):

当试图对象的item特性赋值的时候将会被调用。如果定义了这个方法,不要使用self.attr=value,这样会引起循环调用,正确的做法是使用属性字典做索引。

 def __setattr__(self,key,value):        print('into __setattr__')        self.__dict__[key]=value        st=student('shun')   #构造函数也会调用自己定义的__setattr__st.name              #因此你也可以通过设置__setattr__来让构造函数完成一些额外的工作into __setattr__'shun'st.age=21st.ageinto __setattr__21

应用:模拟实例属性的私有性

class PrivateExc(Exception):        #定义异常类    passclass Privacy(PrivateExc):    def __getattr__(self,attr):        if attr in self.Privates:            raise PrivateExc(attr,self)        else:            return self.__dict__[attr]    def __setattr__(self,attr,value):        if attr in self.Privates:            raise PrivateExc(attr,self)        else:            self.__dict__[attr]=valueclass Test1(Privacy):    Privates=['age']    def __init__(self,name):        self.__dict__['name']=name        a=Test1('shun')a.name'shun'a.age=21PrivateExc: ('age', Test1('shun'))

__add__和__radd__ 和 __iadd__

__radd__是自定义的类操作符,执行“右加”。

当python解释器执行到a+b这样的语句时,首先在查找a中有没有__add__操作符,如果a中没有定义,那么就在b中查找并执行__radd__。

至于__iadd__(),是运算符类operator的成员函数,就是累加操作符的另一种调用形式。a = operator.__iadd__(a, b)就等价于a += b

def __add__(self, other)#该类对象+别的对象时调用    return #加的结果def __radd__(self, other)#别的对象+该类对象时调用    return #加的结果
class Point:    def __init__(self,x,y):        self.x=x        self.y=y       def __add__(self,other):        if isinstance(other,tuple):            return Point(self.x+other[0],self.y+other[1])        else:            return Point(self.x+other.x,self.y+other.y)    def __radd__(self,other):        return Point(self.x+other[0],self.y+other[1])    def __str__(self):        return '(%.2f,%.2f)'%(self.x,self.y)    def __repr__(self):        return '(%.2f,%.2f)'%(self.x,self.y)    a=Point(0,0)b=Point(1.2,1.2)a+=(1,2)(1.00,2.00)(1,2)+a(2.00,4.00)#如果没有__radd__会报错

其他__mul__,__rmul__的用法类似。

__call__

直白来说可以让对象名当作函数名传递参数,继续上面的point类:

 def __call__(self):        return math.sqrt(self.x**2+self.y**2)   a()2.23606797749979

比较重载

def __eq__(self,other):        return (self.x==other.x and self.y==other.y)    def __lt__(self,other):        return self()other()a=Point(0,0)b=Point(1.2,1.2)a==bFalsea

实例:自定义数组类

from operator import mul,addclass MyArray:    '''This an array used for storing numbers'''    def __isNumber(n):        return isinstance(n,(int,float))    def __init__(self,List=[]):        '''filte other types'''        self.__value=list(filter(MyArray.__isNumber,List))    def __del__(self):   #析构函数,释放内部封装的列表        del self.__value    def __str__(self):        return str(self.__value)    def __len__(self):        return len(self.__value)    def append(self,List):#向数组中添加元素        if __isNumber(List):            self.__value.append(List)        elif isinstance(List,list):            self.__value.append(list(filter(MyArray.__isNumber,List)))        else:            pass   #不是数字类型,不做任何处理    def __add__(self,n):        b=MyArray()        if MyArray.__isNumber(n):            b.__value=[i+n for i in self.__value]        elif isinstance(n,MyArray):            m1,m2=self.__value.copy(),n.__value.copy()            while any([m1,m2]):           #自动根据长度进行扩展                b.__value.append((m1.pop(0) if m1 else 0)                                 +(m2.pop(0) if m2 else 0) )        elif isinstance(n,list):          #对列表进行加            tmp=MyArray(n)            return self+tmp        return b     def dot(a,b):#计算内积        return sum(map(mul,a.__value,b.__value))    def __getitem__(self,index):        return self.__value[index]    def __setitem__(self,index,value):        self.__value[index]=value

下一篇:类的设计方法简介

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.souzhinan.com/kj/290336.html