“運算符重載”只是意味著在類方法中攔截內置的操作——當類的實例出現在內置操作中,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循環
例:生成一組平方數
有多個迭代器的對象:要達到多個迭代器的效果,__iter__只需要定義新的狀態對象,而不是返回self。
屬性引用:__getattr__與__setattr__
正常情況下,當我們調用類的方法或屬性時,如果不存在,就會報錯。要避免這個錯誤,除了可以加上一個屬性外,Python還有另一個機制,那就是寫一個__getattr__()方法,動態返回一個屬性。
<code>class student: def __init__(self,name): self.name=name st=student('shun') st.name 'shun' st.age AttributeError: 'student' object has no attribute 'age' #報錯/<code>
這時我們嘗試重寫屬性:
<code>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.age 20/<code>
__setattr__(self, item, value):
當試圖對象的item特性賦值的時候將會被調用。如果定義了這個方法,不要使用self.attr=value,這樣會引起循環調用,正確的做法是使用屬性字典做索引。
<code> def __setattr__(self,key,value): print('into __setattr__') self.__dict__[key]=value st=student('shun') #構造函數也會調用自己定義的__setattr__ st.name #因此你也可以通過設置__setattr__來讓構造函數完成一些額外的工作 into __setattr__ 'shun' st.age=21 st.age into __setattr__ 21/<code>
應用:模擬實例屬性的私有性
<code>class PrivateExc(Exception): #定義異常類 pass class 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]=value class Test1(Privacy): Privates=['age'] def __init__(self,name): self.__dict__['name']=name a=Test1('shun') a.name 'shun' a.age=21 PrivateExc: ('age', Test1('shun'))/<code>
__add__和__radd__ 和 __iadd__
__radd__是自定義的類操作符,執行“右加”。
當python解釋器執行到a+b這樣的語句時,首先在查找a中有沒有__add__操作符,如果a中沒有定義,那麼就在b中查找並執行__radd__。
至於__iadd__(),是運算符類operator的成員函數,就是累加操作符的另一種調用形式。a = operator.__iadd__(a, b)就等價於a += b
<code>def __add__(self, other)#該類對象+別的對象時調用 return #加的結果 def __radd__(self, other)#別的對象+該類對象時調用 return #加的結果/<code>
<code>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__會報錯/<code>
其他__mul__,__rmul__的用法類似。
__call__
直白來說可以讓對象名當作函數名傳遞參數,繼續上面的point類:
<code> def __call__(self): return math.sqrt(self.x**2+self.y**2) a() 2.23606797749979/<code>
比較重載
<code>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==b False a/<code>
實例:自定義數組類
<code>from operator import mul,add class 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/<code>
下一篇:類的設計方法簡介