1.基础知识
python中所谓运算符重载,其实质为在类中定义从新定义其内在的函数,该类函数的显著特征表现为以"__"开始和结束。因此,当实例化该类时,会调用你从新定义的方法。
2.常见的运算符重载方法
__init__ (构造函数);触发方式:X=classname(args),实例化类,传参。 __del__(析构函数);触发方式:X对象收回。 __add__(‘+’运算符);触发方式:X+Y,X+=Y __or__(‘|’运算符);触发方式:X|Y,X=|Y __repr__,__str__(打印、转换);触发方式:print(X)、repr(X)、str(X)。 __call__(函数调用);触发方式:X(args)。 __getattr__(访问属性);触发方式:X.undefinded。 __setattr__(属性赋值);触发方式:X.any=value。 __delattr__(删除属性);触发方式:del X.any。 __getitem__(索引、分片、迭代);触发方式:X[key],X[i:j]、没有重载__iter__方法的for循环和其他迭代操作。 __setitem__(索引赋值和切片赋值);触发方式:X[key]=value,X[i:j]=iterable。 __delitem__(索引删除和分片删除);触发方式:del X[key]、del X[i:j]。 __len__(长度);触发方式:len(X),没有重载__bool__方法的真值测试。 __bool__(布尔测试);触发方式:bool(X)。 __lt__,__gt__,__le__,__ge__,__eq__,__ne__(比较);触发方式:X<Y、X>Y、X<=Y、X>=Y、X==Y、X!=Y。 __radd__(右侧‘+’);触发方式:Other+X。 __iadd__(原位置‘+=’操作);触发方式:X+=Y,若没有重载该方法,则使用__add__。 __iter__,__next__(迭代上下文);触发方式:I=iter(X),next(I);for循环,没有重载__contains()方法的in操作、所有的推导表达式、map(func,iterable)等。 __contains__(成员关系测试);触发方式:item in X (测试item是否在X中)。 __index__(整数值转化);触发方式:hex(x),bin(x),oct(x),O(x)。 __enter__,__exit__(上下文管理器);触发方式:with obj as var:。 __get__、__set__、__delete__(描述符属性);触发方式:X.attr、X.attr=value、del X.attr。 __new__(创建);触发方式:在__init__前创建。
2.1构造函数
class Number: def __init__(self,value): self.data=value num=Number(3)#构造函数主要用于为类传参 print(num.data)
2.2索引和切片:__getitem__、__setitem__
如果定义一个类重载了__getitem__,该方法就会自动被调用并进行实例的索引运算,该方法就像模拟列表的下标访问元素类似,即实例X[i],将X作为第一个参数传入,i作为第二个参数传入。此外该方法还可以用于切片表达式。
在列表中,如L=[1,2,3,4,5]中,切片L[0:1],其中“0:1”传入其实质就是一个切片对象。slice(0,1),该切片对象有3个常见属性,start、stop、step。
__setitem__赋值传入3个参数,self默认,key和value。key相当于X[i]=10,中的i,value相当于10。
for语句通过使用从0到更大索引值,重复对序列进行索引运算,直到检测到超出边界的IndexError异常。因此,该方法可以用于重载迭代方式。若重载了该方法for循环每次都会通过连续的更高的偏移量来调用__getitem__方法。
其中in、列表推导、内置函数map、列表和元组赋值运算以及类型构造都会调用该方法。
class Indexer: data=[1,2,3,4,5] def __getitem__(self, item): if isinstance(item,int): print('indexing:',item) #若传入为一个整数则返回其下标 else: #传统入切片对象返回切片列表和start、stop、step值 print('slicing',item.start,item.stop,item.step) return self.data[item] def __setitem__(self, index, value):#为切片赋值 self.data[index]=value print(self.data) X=Indexer() #实例化对象 print(X[0:2:1]) X[0]=10
class StepperIndex: def __getitem__(self, i): return self.data[i] X=StepperIndex() X.data='spam' for i in X: print(i) print('p' in X)#in关系测试 print([c for c in X])#列表推导 print(list(map(str.upper,X))) #map内置函数 (a,b,c,d)=X list(X) tuple(X)
2.3__index__方法在使用时会返回一个整数值
class C: def __index__(self): return 255 X=C() print(hex(X)) print(bin(X)) print(oct(X))
2.4可迭代对象:__iter__和__next__
尽管__getitem__可以用做迭代,但在python中所有迭代会首先寻找__iter__方法。即若类中重载了__iter__就首先选择该类方法来实现迭代。从技术的角度,迭代上下文是通过将一个可迭代对象传入到内置函数iter中,并调用__iter__方法实现,该方法返回的是一个迭代器对象,python会重复调用__next__方法产生元素。同样该方法也可以进行成员关系in、类型构造、序列赋值运算等,与__getitem__不同的是一个类的__iter__被设计成单次遍历。
class Squares: def __init__(self,start,stop): self.value=start-1 self.stop=stop def __iter__(self): return self def __next__(self): if self.value==self.stop: raise StopIteration self.value+=1 return self.value**2 if __name__=='__main__': for i in Squares(1,5): print(i) X=Squares(1,3) I=iter(X) print(next(I)) print(next(I)) print(next(I)) print([n for n in X]) #单次迭代,返回空[]
当然,在定义类是也可以通过一些方法实现多次迭代,要想实现多次迭代效果,__iter__只需替换为一个新的状态对象,而不是像上例中返回self。
class SkipObject: def __init__(self,wrapped): self.wrapped=wrapped def __iter__(self): return SkipIterator(self.wrapped) class SkipIterator: def __init__(self,wrapped): self.wrapped=wrapped self.offset=0 def __next__(self): if self.offset>=len(self.wrapped): raise StopIteration else: item=self.wrapped[self.offset] self.offset+=2 return item if __name__=='__main__': skipper=SkipObject('abcdef') I=iter(skipper) print(next(I),next(I),next(I)) print([c for c in skipper]) #['a', 'c', 'e']支持多次迭代
2.5成员关系:__contains__,__iter__,__getitem__。
在in测试,__contains__优先级最高,其次__iter__,最后__getitem__。
class Iters: def __init__(self,values): self.data=values def __getitem__(self, item): print('get[%s]:' %item,end='') return self.data[item] def __iter__(self): print('iter=>',end='') self.ix=0 return self def __next__(self): print('next:',end='') if self.ix==len(self.data):raise StopIteration item=self.data[self.ix] self.ix+=1 return item def __contains__(self, x): print('contains:',end='') return x in self.data if __name__=='__main__': X=Iters([1,2,3,4,5]) print(3 in X) #contains:True for i in X: #iter=>next:1| next:2| next:3| next:4| next:5| next: print(i,end='| ') print(' ') print([i**2 for i in X]) #iter=>next:next:next:next:next:next:[1, 4, 9, 16, 25] print(list(map(bin,X))) #iter=>next:next:next:next:next:next:['0b1', '0b10', '0b11', '0b100', '0b101'] I=iter(X) while True: try: print(next(I),end='@') #iter=>next:1@next:2@next:3@next:4@next:5@next: except StopIteration: break
2.6属性访问__getattr__、__setattr__,
__getattr__方法用于属性访问。通俗来说就是对象的点访问操作。如:X.age。当对一个未定义的属性名访问(即没有在类中定义该属性)时,会调用该方法。
__setattr__会拦截默认的属性赋值方法,如果定义或继承了该方法self.attr=value会变成self.__setattr__('attr',value)。允许你的类捕获对属性的改变。但直接使用时要注意。在__setattr__中对任何self属性赋值,都会再次调用__setattr__,这潜在地导致了无穷递归循环。可以通过实例属性的赋值转换为对属性字典的赋值来避免无限循环。还可以通过调用把任意一个属性赋值传递给一个更高级的父类;object.__setattr__(self,attr,value)。
__delattr__将传入属性删除,和__setattr__属性一样,要通过__dict__或父类来避免无穷递归调用
class Empty: def __getattr__(self, attrname): if attrname=='age': return 40 else: raise AttributeError(attrname) def __setattr__(self, attr, value): if attr=='name': object.__setattr__(self,attr,value) self.__dict__[attr]=value else: raise AttributeError(attr+' not allowed') def __delattr__(self, item): if item=='name': self.__dict__[item]=None X=Empty() print(X.age) X.name='hello' print(X.name) del X.name print(X.name) #None
2.7字符串显示:__repr__、__str__
简单理解,定制该两类对象可以改变默认print值。
__str__:会首先被打印操作和str内置函数尝试。通常应当返回一个用户友好的显示。
__repr__:用于所有场景,包括交互式命令行、repr函数、嵌套显示,以及没有重写__str__的打印和str调用。通常应该返回一个编码字符串,可以用来从新创造对象,或者给开发者一个详细显示。
两个方法都必须返回字符串。 其他类型不会被自动转化;
__str__用户友好显示只会应用在对象出现在打印操作时,内嵌在更大对象仍然使用__repr__或默认方式;
2.8右侧加法和原位置加法:__radd__、__iadd__
一般条件下,__add__方法不支持实例对象写在+运算符右侧。如下所示
class Adder: def __init__(self,value=0): self.value=value def __add__(self, other): return self.value+other x=Adder(5) print(x+2) print(2+x) #TypeError: unsupported operand type(s) for +: 'int' and 'Adder'
__radd__:只有当+右侧是实例对象且左侧不是实例对象时,才会调用__radd__方法,在其他所有情况下都是调用__add__
class Commuter1: def __init__(self,val): self.val=val def __add__(self, other): print('add',self.val,other) return other+self.val def __radd__(self, other): print('radd',self.val,other) return other+self.val x=Commuter1(88) y=Commuter1(99) # print(x+1) print(1+x) print(x+y)
在__radd__中重用__add__:
为了在运算中没有位置限制,有时只需从用__add__来实现__radd__。简单来说,有三种实现方式,
直接调用__add__;
交换位置并再次使用加法来间接地触发__add__;
在类的作用域把__radd__=__add__。
class Communter2: def __init__(self,val): self.val=val def __add__(self, other): print('add',self.val,other) return self.val+other def __radd__(self, other): return self.__add__(other) class Communter3: def __init__(self,val): self.val=val def __add__(self, other): print('add',self.val,other) return self.val+other def __radd__(self, other): return self+other class Communter4: def __init__(self,val): self.val=val def __add__(self, other): print('add',self.val,other) return self.val+other __radd__=__add__ x=Communter2(2) y=Communter3(2) z=Communter4(2) print(1+x,2+y,3+z)
为了同时实现+=原位置加法扩展,可以编写一个__iadd__或__add__,如果前者缺省的话就会退而求其次使用后者。
2.9调用表达式:__call__
当调用实例时会使用__call__方法,
class Prod: def __init__(self,value): self.value=value def __call__(self, other): return self.value *other x=Prod(2) print(x(3))