定制類
寫在前面:看到類似__slots__這樣形如__xxx__的變量或者函數(shù)名就要注意烈疚,這些在python中是有特殊用途的。
__slots__上文已講過如何運(yùn)用聪轿,__len__()方法我們也知道是為了讓class作用于len()函數(shù)爷肝。
除此之外,python的class中還有許多這樣的特殊用途的函數(shù)屹电,可以幫助我們定制類阶剑。
__str__
引子:我們先定義一個(gè)Student類,打印出一個(gè)實(shí)例:
但打印結(jié)果是這樣的:
這樣帶一長(zhǎng)串不好看危号,所以這時(shí)候我們來定定義__str__()方法牧愁,返回我們想要的它顯示的結(jié)果。
但當(dāng)我們直接賦值變量外莲,不用print時(shí)猪半,這時(shí)在打印出結(jié)果,
和第一次我們打印出的結(jié)果一樣偷线,這是因?yàn)橹苯语@示的變量調(diào)用的不是__str__()磨确,而是__repr__(),區(qū)別是__str__()返回用戶看到的字符串声邦,而__repr__()返回程序開發(fā)者看到的字符串乏奥,也就是說,__repr__()是為調(diào)試服務(wù)的亥曹。解決辦法是再定義一個(gè)__repr__()邓了。但是通常__str__()和__repr__()代碼都是一樣的恨诱,所以,有個(gè)偷懶的寫法:
__iter__
如果一個(gè)類想被用于for......in.....循環(huán)骗炉,類似list或tuple那樣照宝,就必須實(shí)現(xiàn)一個(gè)__iter__()方法,該方法返回一個(gè)迭代對(duì)象句葵,然后厕鹃,Python的for的循環(huán)就會(huì)不斷調(diào)用該迭代對(duì)象的__next__()方法拿到循環(huán)的下一個(gè)值,直到遇到StopIteration錯(cuò)誤時(shí)退出循環(huán)乍丈。
舉例:以斐波那契數(shù)列為例剂碴,寫一個(gè)Fib類,可以作用于for循環(huán):
作用于for循環(huán):
__getitem__
Fib實(shí)例雖然可以作用于for循環(huán)诗赌,看起來和list有點(diǎn)像汗茄,但,把它當(dāng)成list來使用還不行铭若,因?yàn)樗荒芟駆ist那樣按照下標(biāo)取出元素洪碳,需要實(shí)現(xiàn)__getitem__()方法:
就可以任意取數(shù)了。
但還是不能實(shí)現(xiàn)list的切片方法叼屠,這時(shí)因?yàn)開_getitem__()傳入的參數(shù)可能是一個(gè)int瞳腌,也可能是一個(gè)切片對(duì)象slice,所以要判斷:
現(xiàn)在就可以切片了
但還有一個(gè)問題就是不能對(duì)step參數(shù)處理
也不能對(duì)負(fù)數(shù)作處理镜雨,所以嫂侍,要正確實(shí)現(xiàn)一個(gè)__getitem__()還是有很多工作要做的。
此外荚坞,如果把對(duì)象看成dict挑宠,__getitem__()的參數(shù)也可能是一個(gè)可以看做key的object,例如str.
與之對(duì)應(yīng)的是 __setitem__()方法颓影,把對(duì)象視作list或dict來對(duì)集合賦值各淀,最后,還有一個(gè)__deIitem__()方法诡挂,用于刪除某個(gè)元素碎浇。
總之,通過以上方法璃俗,我們可以定義的類表現(xiàn)的和Python自帶的list奴璃、tuple、dict沒什么區(qū)別城豁,這完全歸功于動(dòng)態(tài)語言的“鴨子類型”苟穆,不需要強(qiáng)制繼承某個(gè)接口。
__getattr__
正常情況下,當(dāng)我們調(diào)用類的方法或?qū)傩詴r(shí)鞭缭,如果不存在剖膳,就會(huì)報(bào)錯(cuò)魏颓。當(dāng)調(diào)用不存在的屬性時(shí)就會(huì)出現(xiàn)AttributeError的錯(cuò)誤岭辣,要避免這個(gè)錯(cuò)誤,我們可以在加上這個(gè)沒有的屬性甸饱,除了這個(gè)方法外沦童,我們還可以有另一個(gè)方法,那就是寫一個(gè)__getattr__()方法叹话,動(dòng)態(tài)返回一個(gè)屬性偷遗,示例如下:
返回函數(shù)也是可以的:
只是調(diào)用方式要變:
注意:只有在沒有找到屬性的情況下,才調(diào)用__getattr__驼壶,已有屬性氏豌,不會(huì)在__getattr__中查找。此外热凹,我們?cè)谌我庹{(diào)用時(shí)都會(huì)返回None,這是因?yàn)槲覀兌x的__getattr__默認(rèn)返回的就是None泵喘。要讓class只相應(yīng)特定的幾個(gè)屬性,我們就要按照約定般妙,拋出AttributeError的錯(cuò)誤:
運(yùn)行下:
這實(shí)際上是把一個(gè)類的所有屬性和方法調(diào)用都全部動(dòng)態(tài)化處理了纪铺,不需要任何特殊手段。
作用:可以針對(duì)完全動(dòng)態(tài)的情況做調(diào)用碟渺。
舉例:現(xiàn)在很多網(wǎng)站都搞REST API, 比如新浪微博鲜锚、豆瓣等,如果要寫SDK苫拍,給每個(gè)URL對(duì)應(yīng)的API都都寫一個(gè)方法芜繁,那就累死了,API一旦改動(dòng)绒极,SDK就要改骏令。利用完全動(dòng)態(tài)的__getattr__就可以寫出一個(gè)鏈?zhǔn)秸{(diào)用:
試試用print打印出來:
返回原理是這樣的:
__call__
當(dāng)我們?cè)谡{(diào)用實(shí)例方法時(shí),用instance.method()來調(diào)用集峦。能不能直接在實(shí)例本身調(diào)用那伏社,答案是可以的,只需要用__call__()方法塔淤。舉例如下:
調(diào)用:
__call__()還可以定義參數(shù)摘昌。對(duì)實(shí)例進(jìn)行直接調(diào)用就好比對(duì)一個(gè)函數(shù)調(diào)用,所以你完全可以把對(duì)象看成函數(shù)高蜂,把函數(shù)看成對(duì)象聪黎,因?yàn)閮烧弑緛砭蜎]有本質(zhì)的區(qū)別。
如果把對(duì)象看成函數(shù),那么函數(shù)本身是可以在運(yùn)行期動(dòng)態(tài)創(chuàng)建出來的稿饰,因?yàn)轭惖膶?shí)例都是運(yùn)行期創(chuàng)建出來的锦秒。這么一來,我們就模糊了對(duì)象和函數(shù)的界限喉镰。
那么旅择。我們?nèi)绾闻袛嘁粋€(gè)變量是對(duì)象還是函數(shù),其實(shí)侣姆,我們只要判斷他是否能被調(diào)用就可以了生真,能被調(diào)用的就是一個(gè)Callable()對(duì)象,通過callable()我們就可以判斷一個(gè)對(duì)象是否能被調(diào)用捺宗。
總結(jié):
完全動(dòng)態(tài)調(diào)用特性:把一個(gè)類的所有屬性和方法調(diào)用全部動(dòng)態(tài)化處理?
?__call__(): 用于實(shí)例自身的調(diào)用柱蟀,達(dá)到()調(diào)用的效果 , 即可以把此類的對(duì)象當(dāng)作函數(shù)來使用蚜厉,相當(dāng)于重載了括號(hào)運(yùn)算符??
__getattr__(): 當(dāng)調(diào)用不存在的屬性時(shí)調(diào)用此方法來嘗試獲得屬性
鏈?zhǔn)竭\(yùn)行過程:
分析