長文---Python運算符重載

“運算符重載”只是意味著在類方法中攔截內置的操作——當類的實例出現在內置操作中,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__的基本用法:

長文---Python運算符重載

這裡出現了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__,起作用是通過切片的方法設置索引值:


長文---Python運算符重載

__getitem__的索引迭代:

如果定義了此方法,for循環每次都會調用這個方法,從而產生更高的偏移值,甚至可以自定義偏移值遞增方法:


長文---Python運算符重載

自定義迭代器

為了更好理解,先介紹一組基本概念:

  • 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循環

例:生成一組平方數


長文---Python運算符重載

有多個迭代器的對象:要達到多個迭代器的效果,__iter__只需要定義新的狀態對象,而不是返回self。


長文---Python運算符重載

屬性引用:__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>

下一篇:類的設計方法簡介


分享到:


相關文章: