1??普通運(yùn)算符的重載
運(yùn)算符重載并不是一個(gè)很簡單的話題启搂,但大多數(shù)的特殊情況并不實(shí)用窖式,本文只討論較常用的運(yùn)算符重載。首先痊项,Python中的各種數(shù)學(xué)運(yùn)算锅风,包括加減乘除等,都對應(yīng)著某個(gè)方法調(diào)用鞍泉。如1 + 2實(shí)際上對應(yīng)著形如1.__add__(2)的調(diào)用皱埠。所以,在程序中咖驮,用戶也可以修改這些函數(shù)边器,從而自定義這些運(yùn)算符號的行為。下面以__add__與__radd__為例托修,討論這樣的操作忘巧。
當(dāng)一個(gè)類重寫了__add__方法后,就可以對類實(shí)例使用加號運(yùn)算诀黍,__add__方法應(yīng)返回一個(gè)運(yùn)算后的結(jié)果:
class Test:
??? def __add__(self, other):
??????? return 1 + other
print(Test() + 10)
__add__方法有兩個(gè)形參,分別對應(yīng)于加號前后的兩個(gè)值仗处,所以上例中的__add__只是簡單的把加號后值加1的結(jié)果返回眯勾,輸出為11。
__radd__方法不太常用婆誓,這里僅作簡要說明吃环。當(dāng)執(zhí)行加法運(yùn)算時(shí),實(shí)際上是以self.__add__(other)的形式調(diào)用了加號左值的__add__方法洋幻,而如果由于加號兩邊對象不一致(如numpy中的ndarray可以與Python list進(jìn)行運(yùn)算)郁轻,加號左值并沒有定義__add__方法時(shí),此時(shí)就會顛倒加號的左右值,并嘗試調(diào)用加號右值的__radd__方法好唯。例如上文中的Test() + 10竭沫,如果換成10 + Test(),則調(diào)用將失敗骑篙,因?yàn)閕nt類型不能以Test()作為第二參數(shù)進(jìn)行__add__方法調(diào)用蜕提。此時(shí),如果Test類還定義了__radd__方法:
class Test:
??? def __add__(self, other):
??????? return 1 + other
??? def __radd__(self, other):
??????? return 1 + other
則此時(shí)如果進(jìn)行10 + Test()靶端,由于10.__add__(Test())將調(diào)用失敗谎势,那么就會嘗試Test().__radd__(10),顛倒左右參數(shù)杨名,并調(diào)用加號右值的__radd__方法脏榆。故大部分情況下,__add__和__radd__方法的定義都是類似的台谍。在實(shí)際情況中须喂,如果考慮到可能存在混合類型運(yùn)算時(shí),則可能需要考慮再定義__radd__方法典唇。
2??增強(qiáng)賦值運(yùn)算符的重載
增強(qiáng)賦值運(yùn)算在很多編程語言中都有體現(xiàn)镊折,其是一類由運(yùn)算符加上等號組成的自身運(yùn)算符。此類運(yùn)算符同時(shí)執(zhí)行運(yùn)算與賦值操作介衔,即對self本身進(jìn)行運(yùn)算與賦值恨胚。增強(qiáng)賦值運(yùn)算符與普通運(yùn)算符在重載時(shí)唯一的區(qū)別在于:普通運(yùn)算符返回的是運(yùn)算結(jié)果,而增強(qiáng)賦值運(yùn)算符返回的一定是self炎咖,這個(gè)self的值已經(jīng)通過運(yùn)算而改變赃泡,返回的self將通過賦值覆蓋掉原先的self。下面以__iadd__方法為例討論增強(qiáng)賦值運(yùn)算符的重載:
class Test:
??? def __init__(self, num):
??????? self.num = num
??? def __add__(self, value):
??????? return self.num + value
??? def __iadd__(self, value):
??????? self.num += value
??????? return self
testObj = Test(2)
testObj += 3
print(testObj.num)
上例中乘盼,__iadd__直接對self進(jìn)行運(yùn)算操作升熊,改變了self中的某些值,然后返回self本身绸栅。所以返回的這個(gè)self在增強(qiáng)賦值運(yùn)算之后會直接覆蓋掉運(yùn)算符左值级野,從而實(shí)現(xiàn)自身值的改變。而對比__add__方法粹胯,其僅僅返回了一個(gè)加法運(yùn)算的結(jié)果蓖柔,并不改變self本身。
3??__repr__與__str__的重載
__repr__與__str__分別對應(yīng)著Python中的repr與str函數(shù)調(diào)用风纠。關(guān)于這兩個(gè)函數(shù)的區(qū)別况鸣,一般上認(rèn)為:repr是生成“適合解釋器閱讀的格式”,而str則生成“適合用戶閱讀的格式”竹观,這樣的解釋很模糊镐捧,且在實(shí)際使用中并不需要特別關(guān)注這兩個(gè)函數(shù)的功能潜索。因?yàn)榻^大多數(shù)情況下,用戶一般都只會調(diào)用str函數(shù)而非repr函數(shù)懂酱,且print函數(shù)的輸出樣式也與str函數(shù)的返回值一致竹习,故以下主要針對__str__方法進(jìn)行討論。
在用戶自定義的類型中玩焰,__str__方法一般只與print函數(shù)連用由驹,起到明確、美化輸出的作用昔园。默認(rèn)情況下蔓榄,如果不定義__str__方法,則輸出某個(gè)類實(shí)例時(shí)會顯示“”這樣的字符串默刚,這就是print函數(shù)對當(dāng)前對象調(diào)用__str__方法的結(jié)果甥郑。而如果需要美化輸出,就可以重寫__str__方法荤西,返回一個(gè)期望的字符串:
class Test:
??? def __init__(self, num):
??????? self.num = num
??? def __str__(self):
??????? return '[%d]' % self.num
print(Test(2))
上例中澜搅,通過重載__str__方法,使得print(Test(2))時(shí)邪锌,不再輸出“”這樣的字符串勉躺,而是輸出了自定義的“[2]”字符串。
由于一般情況下均不關(guān)心__str__與__repr__的區(qū)別觅丰,故在重載__str__方法后饵溅,往往只需要將__repr__方法也綁定到__str__方法上即可:
__repr__ = __str__
同理,重載__repr__方法妇萄,然后__str__ = __repr__蜕企,也是可以的。
4??__call__的重載
__call__方法的重載應(yīng)用場合較少冠句,本文只做簡要討論轻掩。
首先,Python中針對一個(gè)對象懦底,可以使用多種“符號后綴語法”唇牧,這些語法背后都分別對應(yīng)著一個(gè)或一系列的方法調(diào)用。如一個(gè)對象后接“[...]”聚唐,則對應(yīng)著__*item__方法系列丐重;后接“.”,則對應(yīng)著__*attr__與__getattribute__方法系列拱层;而如果一個(gè)對象后接“(...)”弥臼,則對應(yīng)著代表函數(shù)調(diào)用的__call__方法宴咧。
當(dāng)重載一個(gè)類的__call__方法后根灯,這個(gè)類的對象就可以當(dāng)做一個(gè)函數(shù)一樣被調(diào)用,也可為__call__方法定義形參,則實(shí)際調(diào)用中就可以傳入相對應(yīng)的實(shí)參:
class Test:
??? def __init__(self, num):
??????? self.num = num
??? def __call__(self, value):
??????? print(self.num, value)
Test(2)('call')
當(dāng)__call__被定義后烙肺,Test(2)作為一個(gè)類實(shí)例纳猪,就可以當(dāng)做函數(shù)一樣調(diào)用,并可傳入相應(yīng)的參數(shù)桃笙。上述代碼的調(diào)用結(jié)果即為輸出“2 call”氏堤。
2018年6月于蘇州