Python学习笔记整理(十五)类的编写细节
類(lèi)代碼編寫(xiě)細(xì)節(jié)
一、class語(yǔ)句
一般形式
class??? <name>(superclass,...):
??? data=value
??? def mothod(self,...):
??? ??? self.member=value
在class語(yǔ)句內(nèi),任何賦值語(yǔ)句都會(huì)產(chǎn)生類(lèi)屬性。
類(lèi)幾乎就是命名空間,也就是定義變量名(屬性)的工具,把數(shù)據(jù)和邏輯導(dǎo)出給客戶端。
怎么樣從class語(yǔ)句得到命名空間的呢?
過(guò)程如下。就像模塊文件,位于class語(yǔ)句主體中的語(yǔ)句會(huì)建立起屬性。當(dāng)python執(zhí)行class語(yǔ)句時(shí)(不是調(diào)用類(lèi))
會(huì)從頭到尾執(zhí)行其主體內(nèi)的所有語(yǔ)句。在這個(gè)過(guò)程中,進(jìn)行賦值運(yùn)算會(huì)在這個(gè)類(lèi)的作用域中創(chuàng)建變量名,從而成為對(duì)應(yīng)
類(lèi)對(duì)象中的屬性。因?yàn)?#xff0c;類(lèi)就像模塊和函數(shù):
*就像函數(shù)一樣。class語(yǔ)句是作用域,由內(nèi)嵌的賦值語(yǔ)句建立變量名,就存在這個(gè)本地作用域內(nèi)。
*就像模塊內(nèi)的變量名,在class語(yǔ)句內(nèi)賦值的變量名會(huì)變成類(lèi)對(duì)象中的屬性。
class是復(fù)合語(yǔ)句,任何種類(lèi)的語(yǔ)句都可以位于其主體內(nèi):print ,=,if,def等。當(dāng)class語(yǔ)句自身運(yùn)行時(shí),class語(yǔ)句內(nèi)的所有
語(yǔ)句都會(huì)執(zhí)行。在class語(yǔ)句內(nèi)賦值的變量名會(huì)創(chuàng)建類(lèi)屬性,而內(nèi)嵌的def語(yǔ)句則會(huì)創(chuàng)建類(lèi)方法,其他的賦值語(yǔ)句也可以制作屬性。
class頂層的賦值語(yǔ)句定義的屬性可以用于管理貫穿所有實(shí)例的信息。
二、方法
方法位于class語(yǔ)句的主體內(nèi)是由def語(yǔ)句建立的函數(shù)對(duì)象。抽象角度,方法替實(shí)例對(duì)象提供了要繼承的行為。程序角度,
方法的工作方式與簡(jiǎn)單函數(shù)完全一致,只有一個(gè)重要差異:方法的第一個(gè)參數(shù)總是接受方法調(diào)用的隱形主體,也就是實(shí)例對(duì)象。
Python會(huì)自動(dòng)把實(shí)例方法的調(diào)用對(duì)應(yīng)到類(lèi)方法函數(shù)。
方法調(diào)用需要通過(guò)實(shí)例,如:
instance.method(arg...)
這會(huì)自動(dòng)翻譯成以下形式的類(lèi)方法函數(shù)調(diào)用:
class.method(instance,args...)
class通過(guò)Python繼承搜索流程找出方法名稱(chēng)所在之處。事實(shí)上,這兩種調(diào)用形式在Python都有效。
類(lèi)方法的第一個(gè)參數(shù)通常稱(chēng)為self。這個(gè)參數(shù)提供方法一個(gè)鉤子,從而返回調(diào)用的主體,也就是實(shí)例對(duì)象:
因?yàn)轭?lèi)可以產(chǎn)生許多實(shí)例對(duì)象,所以需要這個(gè)參數(shù)來(lái)管理每個(gè)實(shí)例彼此各不相同的數(shù)據(jù)。
Python中self一定要在程序代碼中明確地寫(xiě)出:方法一定要通過(guò)self來(lái)取出或修改由當(dāng)前方法調(diào)用或正在處理的實(shí)例屬性。
這個(gè)變量名的存在,會(huì)讓你明確腳本中使用的是實(shí)例屬性名稱(chēng),而不是本地作用域或全局作用域中的變量名。
1、調(diào)用超類(lèi)的構(gòu)造器
方法一般是通過(guò)實(shí)例調(diào)用的。不過(guò)通過(guò)類(lèi)調(diào)用【class.method(instance實(shí)例,args...)】方法也扮演了一些特殊角色。
常見(jiàn)的如構(gòu)造器方法。像其他屬性一樣___init__方法是由繼承進(jìn)行查找。也就是說(shuō),在構(gòu)造時(shí),Python會(huì)找出并且只調(diào)用
一個(gè)__init__。如果要保證子類(lèi)的構(gòu)造方法也會(huì)執(zhí)行超類(lèi)構(gòu)造器的邏輯,一般都必須通過(guò)類(lèi)明確地調(diào)用超類(lèi)的__init__方法。
class Super:
??? def __init__(self,x):
??? ??? ...default code...
class Sub(Super):
??? def __init__(self,x,y):
??? ??? Super.__init__(self,x)? ###還是有用到的地方。
??? ??? ...custom code...
I=Sub(1,2)
疑問(wèn):子類(lèi)__init__方法不會(huì)繼承超類(lèi)的嗎?需要是明確手動(dòng)繼承?這個(gè)是重載吧?
動(dòng)手驗(yàn)證
>>> class Super:
...???? def __init__(self):
...???????????? self.name='diege'
...
>>> class Sub(Super):
...???? def setage(self,age):
...???????????? self.age=age
...
>>> x=Sub()?
>>> x.name
'diege
實(shí)驗(yàn)證明子類(lèi)的__init__方法也會(huì)繼承,沒(méi)有任何特殊,超類(lèi)的任何屬性子類(lèi)都會(huì)繼承,前面的例子是重載。
前面的例子是代碼有可能直接調(diào)用運(yùn)算符重載方法的環(huán)境之一。
如果真的想運(yùn)行超類(lèi)的構(gòu)造方法并做適當(dāng)?shù)男薷?#xff0c;自然只能用這種方法進(jìn)行調(diào)用:沒(méi)有這樣的調(diào)用,子類(lèi)會(huì)
完全取代(覆蓋)超類(lèi)的構(gòu)造器,或者子類(lèi)沒(méi)有設(shè)置__init__構(gòu)造器的情況下完全繼承超類(lèi)的構(gòu)造器方法。
2、其他方法調(diào)用的可能。
這種通過(guò)類(lèi)調(diào)用方法的模式(類(lèi)中調(diào)用類(lèi)的方法(不一定自己)),是擴(kuò)展繼承方法行為(而不是完全取代)的
一般基礎(chǔ)。Python2.2新增的選項(xiàng):靜態(tài)方法、可以編寫(xiě)不預(yù)期第一個(gè)參數(shù)為實(shí)例對(duì)象的方法。這類(lèi)方法可像簡(jiǎn)單
的無(wú)實(shí)例的函數(shù)那樣運(yùn)作,其變量名屬于其所在類(lèi)的作用域。不過(guò),這是高級(jí)的選用擴(kuò)展功能。通常情況,一定要
為方法傳入實(shí)例,無(wú)論通過(guò)實(shí)例還是類(lèi)調(diào)用。
3、繼承
像class語(yǔ)句這樣的命名空間工具的重點(diǎn)就是支持變量名繼承。這里擴(kuò)展關(guān)于屬性繼承的一些機(jī)制和角色。
在Python中,當(dāng)對(duì)對(duì)象進(jìn)行點(diǎn)號(hào)運(yùn)算時(shí),就會(huì)發(fā)生繼承,而且涉及到搜索屬性定義樹(shù)(一或多個(gè)命名空間)。每次
使用obecj.attr形式的表達(dá)式時(shí)(objecj是實(shí)例或類(lèi)對(duì)象),Python會(huì)從頭到尾搜索命名空間樹(shù),先從對(duì)象開(kāi)始,
找到第一個(gè)attr為止。這包括在方法中對(duì)self屬性的引用。因?yàn)闃?shù)中較低的定義會(huì)覆蓋較高的定義,繼承構(gòu)成了
專(zhuān)有化的基礎(chǔ)。
4、屬性樹(shù)的構(gòu)造
命名空間樹(shù)構(gòu)造以及填入變量名的方式,通常來(lái)說(shuō):
【*】實(shí)例屬性是由對(duì)方法內(nèi)self屬性進(jìn)行賦值運(yùn)算而生成的
【*】類(lèi)屬性是通過(guò)class語(yǔ)句內(nèi)頂層的語(yǔ)句(賦值語(yǔ)句)而生成的
【*】超類(lèi)鏈接通過(guò)class語(yǔ)句首行的括號(hào)內(nèi)列出類(lèi)而生成的。
5、繼承方法的專(zhuān)有化
繼承樹(shù)搜索模式,變成了將系統(tǒng)專(zhuān)有化的最好方式。因?yàn)槔^承會(huì)先在子類(lèi)尋找變量名,然后才查找超類(lèi),子類(lèi)就可以對(duì)超類(lèi)的屬性重新定義來(lái)取代默認(rèn)的行為。把系統(tǒng)做成類(lèi)的層次,再新增外部的子類(lèi)來(lái)對(duì)其進(jìn)行擴(kuò)展,而不是在原處修改已存在的邏輯。重新定義繼承變量名的概念因出了各種專(zhuān)有化技術(shù)。
>>> class Super:
...???? def method(self):
...???????????? print 'in Super.method'
...
>>> class Sub(Super):?????????????????
...???? def method(self):?????????????
...???????????? print 'start Sub.method'
...???????????? Super.method(self)??? #直接調(diào)用超類(lèi)的方法
...???????????? print 'ending Sub.method'????
...
>>> Z=Super()
>>> Z.method()
in Super.metho
>>> X=Sub()
>>> X.method()
start Sub.method
in Super.method
ending Sub.method
直接調(diào)用超類(lèi)的方法是這里的重點(diǎn)。Sub類(lèi)以其專(zhuān)有化的版本取代了Super的方法函數(shù)。但是取代時(shí),Sub又回調(diào)了Super所導(dǎo)出的版本,從而實(shí)現(xiàn)了默認(rèn)的行為,換句話說(shuō),Sub.mothod只是擴(kuò)展了Super.mothod的行為,而不是完全取代他。這種擴(kuò)展編碼模式常常用于構(gòu)造器方法。
6、類(lèi)接口技術(shù)
class Super:
??????? def method(self):
??????????????? print "in Super.method"
??????? def delegate(self):
??????????????? self.action()
class Inheritor(Super):
??????? pass
class Replacer(Super):
??????? def method(self):
??????????????? print "in Replacer.method"
class Extender(Super):
??????? def method(self):
??????????????? print "starting Extender.method"
??????????????? Super.method(self)
??????????????? print "ending Extender.method"
class Provider(Super):
??????? def action(self):
??????????????? print "in Provider.method"
if __name__=='__main__':
??????? for C in (Inheritor,Replacer,Extender):
??????????????? print '\n'+C.__name__+'...'
??????????????? C().method() ??? #C后面的括號(hào)表面是類(lèi)時(shí)實(shí)例,這里是創(chuàng)建實(shí)例和方法調(diào)用一起了。分解C=Inheritor(),C.method()
??? ??? ??? ???
??????????????? print '\nProvider...'
??????????????? x=Provider()??? #創(chuàng)建實(shí)例對(duì)象
??????????????? x.delegate()??? #實(shí)例對(duì)象條用delegate方法,delegate方法通過(guò)實(shí)例的action方法實(shí)現(xiàn)
結(jié)果
Inheritor...
in Super.method
Provider...
in Provider.method
Replacer...
in Replacer.method
Provider...
in Provider.method
Extender...
starting Extender.method
in Super.method
ending Extender.method
Provider...
in Provider.method
說(shuō)明
Super
??? 定義了一個(gè)method函數(shù)和一個(gè)delegate函數(shù)
Inheritor
??? 沒(méi)有提供任何新的變量名,因此獲得Super中定義的一切內(nèi)容
Replacer
??? 用自己的版本覆蓋Super的method
Extender
??? 覆蓋并回調(diào)默認(rèn)的method,從而定制Super的method
Provider
??? 現(xiàn)實(shí)Super的delegate方法預(yù)期的action方法.這里有點(diǎn)不好理解
Provider繼承了Super的method和delegate方法并增加了action方法,而delegate方法是調(diào)用實(shí)例的action方法實(shí)現(xiàn)的。
尾部的自我測(cè)試程序代碼在for循環(huán)中建立了三個(gè)不同的實(shí)例。因?yàn)轭?lèi)是對(duì)象,可以將他們放入元組中,并可以通過(guò)這樣的方式創(chuàng)建實(shí)例。類(lèi)有特殊的屬性__name__類(lèi)的名字,就像模塊一樣有__name__屬性模塊的名字。類(lèi)中默認(rèn)為類(lèi)行首行中的類(lèi)名稱(chēng)的字符串。
7、抽象超類(lèi)
上例中Provider類(lèi)如何工作的?當(dāng)通過(guò)Provider類(lèi)的實(shí)例調(diào)用delegate方法時(shí),兩個(gè)獨(dú)立的繼承搜索會(huì)發(fā)生:
(1)最初x.delegate的調(diào)用中,Pythn會(huì)搜索Provider實(shí)例和它上層的對(duì)象。知道在Super中找到delegate方法。實(shí)例x
會(huì)像往常一樣傳遞給這個(gè)方法self參數(shù)
(2)Super.delegate方法中,self.action會(huì)對(duì)self及其它上層的對(duì)象啟動(dòng)新的獨(dú)立繼承搜索,因?yàn)閟elf指的是Provider
實(shí)例,就會(huì)找到Provider中的action方法。
抽象類(lèi)就是會(huì)調(diào)用方法的類(lèi),但沒(méi)有繼承或定義該方法,而是期待該方法由子類(lèi)填補(bǔ)。當(dāng)行為無(wú)法預(yù)測(cè),非得等到更為具體的子類(lèi)編寫(xiě)時(shí)才知道,可用這種方式把類(lèi)通用化。這種“填空”的代碼結(jié)構(gòu)一般就是OOP軟件的框架。從delegate方法的角度來(lái)看,這個(gè)例子中的超類(lèi)有時(shí)也稱(chēng)作是抽象類(lèi)--也就是類(lèi)的部分行為默認(rèn)是由其子類(lèi)所提供的。如果預(yù)期的方法沒(méi)有在子類(lèi)定義,當(dāng)繼承搜索失敗時(shí),Python會(huì)引發(fā)為定義
變量名的異常。類(lèi)的編寫(xiě)者偶爾會(huì)使用assert語(yǔ)句,使這種子類(lèi)需求更為明顯,或者引發(fā)內(nèi)置的異常NotImplementedError
class Super:
??????? def method(self):
??????????????? print "in Super.method"
??????? def delegate(self):
??????????????? self.action()
??? ??? def action(self):
??????????????? assert 0, 'action must be defind'
如果表達(dá)式運(yùn)算結(jié)構(gòu)為假,就會(huì)引發(fā)帶有錯(cuò)誤信息的異常。在這里表達(dá)式總是為假(0)。因?yàn)槿绻麤](méi)有方法重新定義,
繼承就會(huì)找到這里的版本,觸發(fā)錯(cuò)誤信息。
三、運(yùn)算符重載
重載的關(guān)鍵概念
*運(yùn)算符重載讓類(lèi)攔截常規(guī)的Python運(yùn)算。
*類(lèi)可重載所有Python表達(dá)式運(yùn)算。
*類(lèi)可重載打印,函數(shù)調(diào)用,屬性點(diǎn)號(hào)運(yùn)算等運(yùn)算。
*重載使類(lèi)實(shí)例的行為像內(nèi)置類(lèi)型。
*重載是通過(guò)提供特殊名稱(chēng)的類(lèi)方法來(lái)實(shí)現(xiàn)的。
如果類(lèi)中提供了某些特殊名稱(chēng)的方法,當(dāng)類(lèi)實(shí)例出現(xiàn)在運(yùn)算有關(guān)的表達(dá)式的時(shí)候,Python就會(huì)自動(dòng)調(diào)用這些方法。
類(lèi)Number放到number.py模塊中:
class Number():
??????? def __init__(self,start):
??????????????? self.data=start
??????? def __sub__(self,other):
??????????????? return Number(self.data-other)
>>> from number import Number
>>> X=Number(5)
>>> Y=X-2??????
>>> Y.data
3
>>> Z=X+5
Traceback (most recent call last):
? File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'instance' and 'int'
這個(gè)例子Number類(lèi)提供一個(gè)方法來(lái)攔截實(shí)例的構(gòu)造器(__init__),此外還有一個(gè)方法捕捉減法表達(dá)式(__sub__).
這種特殊的方法是鉤子,可與內(nèi)置運(yùn)算相綁定。
1、 常見(jiàn)的運(yùn)算符重載方法
方法??? ??? 重載??? ??? 調(diào)用
__init__??? 構(gòu)造器方法??? 對(duì)象建立:X=Class()
__del__??? ??? 析構(gòu)方法??? 對(duì)象收回
__add__??? ??? 運(yùn)算符+??? ??? X+Y,X+=Y
__sub__??? ??? 運(yùn)算符-??? ??? X-Y,X-=Y
__or__??? ??? 運(yùn)算符|(位OR)??? X|Y X|=Y
__repr__,__str__ 打印,轉(zhuǎn)換??? print X、repr(X)、str(X)
__call__??? 函數(shù)調(diào)用??? X()
__getattr__??? 點(diǎn)號(hào)運(yùn)算??? X.undefined
__setattr__??? 屬性賦值語(yǔ)句??? X.any=Value
__getitem__??? 索引運(yùn)算??? X[key],沒(méi)有__iter__時(shí)的for循環(huán)和其他迭代器
__setitem__??? 索引賦值語(yǔ)句??? X[key]=value
__len__??? ??? 長(zhǎng)度??? ??? ??? len(X),真值測(cè)試
__cmp__??? ??? 比較??? ??? ??? X==Y,X
__lt__??? ??? 特定的比較??? ??? X<Y(or else __cmp__)
__eq__??? ??? 特定的比較??? ??? X==Y(or else __cmp__)
__radd__??? 左側(cè)加法 +??? ??? Noninstance + X
__iadd__??? 實(shí)地(增強(qiáng)的)的加法??? X+=Y(or else __add__)
__iter__??? 迭代環(huán)境??? ??? 用于循環(huán),測(cè)試,理解,列表,映射及其他
所有重載方法的名稱(chēng)前后都有兩個(gè)下劃線字符,以便把同類(lèi)中定義的變量名區(qū)別開(kāi)來(lái)。特殊方法名稱(chēng)和表達(dá)式或運(yùn)算的映射關(guān)系,是由Python語(yǔ)言預(yù)先定義好的。
所有運(yùn)算符重載的方法都是選用的:如果沒(méi)有寫(xiě)某個(gè)方法,那么定義的類(lèi)就不支持該運(yùn)算。多數(shù)重載方法只用在需要對(duì)象行為表現(xiàn)得就像內(nèi)置函數(shù)一樣的高級(jí)程序中。然而,__init__構(gòu)造方法常出現(xiàn)在絕大多數(shù)類(lèi)中。
__getitem__攔截索引運(yùn)算
__getitem__方法攔截實(shí)例的索引運(yùn)算。當(dāng)實(shí)例X出現(xiàn)X[i]這樣的索引運(yùn)算中時(shí),Python會(huì)調(diào)用這個(gè)實(shí)例繼承的__getitem__方法。
(如果有),把X作為第一個(gè)參數(shù)傳遞,并且放括號(hào)內(nèi)的索引值傳遞給第二個(gè)參數(shù)。
>>> class index:
...???? def __getitem__(self,index):
...???????????? return index**2?
...
>>> X=index()
>>> X[2]
4
>>> for i in range(5):
...???? print?? X[i],
...
0 1 4 9 16
__getitem__和__iter__實(shí)現(xiàn)迭代
for循環(huán)的作用是從0到更大的索引值,重復(fù)對(duì)序列進(jìn)行索引運(yùn)算,直到檢測(cè)到超出邊界的異常。
__getitem__也可以是Python中一種重載迭代的方式,如果定義了這個(gè)方法,for循環(huán)每次循環(huán)時(shí)都會(huì)調(diào)用類(lèi)的__getitem__
>>> class stepper:
...???? def __getitem__(self,i):
...???????????? return self.data[i]
...
>>> X=stepper
>>> X=stepper()
>>> X.data='diege'
>>> X[1]
'i'
>>> for item in X:
...???? print item,
...
d i e g e
任何支持for循環(huán)的類(lèi)也會(huì)自動(dòng)支持Python所有迭代環(huán)境,包括成員關(guān)系測(cè)試in,列表解析,內(nèi)置函數(shù)map,列表和元組賦值運(yùn)算以及類(lèi)型構(gòu)造方法也會(huì)自動(dòng)調(diào)用__getitem__(如果定義的話)。如今,Python中所有的迭代環(huán)境都會(huì)先嘗試__iter__方法,再嘗試__getitem__。如果對(duì)象不支持迭代協(xié)議,就會(huì)嘗試索引運(yùn)算。
從技術(shù)角度來(lái)將,迭代環(huán)境是通過(guò)調(diào)用內(nèi)置函數(shù)iter去嘗試尋找__iter__方法來(lái)實(shí)現(xiàn)的,而這種方法應(yīng)該返回一個(gè)迭代器對(duì)象。
如果已經(jīng)提供了,Python就會(huì)重復(fù)調(diào)用這個(gè)迭代器對(duì)象的next方法,直到發(fā)生StopIteration異常。如果沒(méi)有找到__iter__方法
,Python會(huì)改用__getitem__機(jī)制,就像之前那樣通過(guò)偏移量重復(fù)索引,直到引發(fā)IndexError異常。
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
>>> from test29 import Squares
>>> for i in Squares(1,5):???
...???? print i,?????????
...
1 4 9 16 25
迭代器對(duì)象就是實(shí)例self,因?yàn)閚ext方法是這個(gè)類(lèi)的一部分。在較為復(fù)雜的的場(chǎng)景中,迭代器對(duì)象可定義為個(gè)別的類(lèi)或?qū)ο?#xff0c;有自己的狀態(tài)信息,對(duì)相同數(shù)據(jù)支持多種迭代。以Python raise語(yǔ)句發(fā)出信號(hào)表示迭代結(jié)束。__iter__對(duì)象會(huì)在調(diào)用過(guò)程中明確地保留狀態(tài)信息。所以比__getitem__具體更好的通用性。__iter__迭代器比__getitem__更復(fù)雜和難用。迭代器是用來(lái)迭代,不是隨機(jī)的索引運(yùn)算。事實(shí)上,迭代器根本沒(méi)有重載索引表達(dá)式.
>>> X=Squares(1,5)
>>> X[1]
Traceback (most recent call last):
? File "<stdin>", line 1, in <module>
AttributeError: Squares instance has no attribute '__getitem__'
__iter__機(jī)制是在__getitem__中所見(jiàn)到的其他所有迭代環(huán)境的實(shí)現(xiàn)方式(成員關(guān)系測(cè)試,類(lèi)型構(gòu)造器,序列賦值運(yùn)算)。和__getitem__不同的是,__iter__只循環(huán)一次,而不是循環(huán)多次,循環(huán)之后就變?yōu)榭?#xff0c;每次新的循環(huán),都得創(chuàng)建一個(gè)新的。
>>> X=Squares(1,5)
>>> [n for n in X]
[1, 4, 9, 16, 25]
>>> [n for n in X]
[]
如果使用生成器函數(shù)編寫(xiě),這個(gè)例子可能更簡(jiǎn)單
>>> from __future__ import generators
>>> def gsquares(start,stop):???????
...???? for i in range(start,stop+1):
...???????????? yield i**2???????????
...
>>> for i in gsquares(1,5):
...???? print i,??????????
...
1 4 9 16 25
和類(lèi)不同的是,這個(gè)函數(shù)會(huì)自動(dòng)在迭代中存儲(chǔ)存其狀態(tài)。這是假設(shè)的例子,實(shí)際上,可以跳過(guò)這兩種技術(shù),只用for循環(huán),map或列表解析
一次創(chuàng)建這個(gè)列表。
>>> [x**2 for x in range(1,6)]
[1, 4, 9, 16, 25
有多個(gè)迭代器的對(duì)象。
__getattr__和__setattr__捕捉屬性的引用。
__getattr__方法是攔截屬性點(diǎn)號(hào)運(yùn)算。__getattr__可以作為鉤子來(lái)通過(guò)通用的方式相應(yīng)屬性請(qǐng)求。
>>> class empty:
...???? def __getattr__(self,attrname):
...???????????? if attrname=="age":
...???????????????????? return 40
...???????????? else:
...???????????????????? raise AttributeError,attrname
...
>>> X=empty()
>>> X.age
40
這里,empty類(lèi)和其實(shí)例X本身并沒(méi)有屬性。所以對(duì)X.age的存取會(huì)轉(zhuǎn)換至__getattr__方法,self則賦值為實(shí)例(X),
而attrname則賦值為未定義的屬性名稱(chēng)字符串("age"),這個(gè)類(lèi)傳回一個(gè)實(shí)際值作為X.age點(diǎn)號(hào)表達(dá)式的結(jié)果(40),
讓age看起來(lái)像實(shí)際的屬性。實(shí)際上,age變成了動(dòng)態(tài)計(jì)算的屬性。
__setattr__會(huì)攔截所有賦值語(yǔ)句,一般不用。
__repr__,__str__ 打印,轉(zhuǎn)換??? print X、repr(X)、str(X)
__call__攔截調(diào)用:如果定義了,Python就會(huì)為實(shí)例應(yīng)用函數(shù)調(diào)用表達(dá)式運(yùn)行__call__方法。
當(dāng)需要為函數(shù)的API編寫(xiě)接口時(shí),__call__就變得很用有:這可以編寫(xiě)遵循所需要的函數(shù)來(lái)調(diào)用接口對(duì)象。
函數(shù)接口和回調(diào)代碼
__del__是析構(gòu)器
每當(dāng)實(shí)例產(chǎn)生時(shí),就會(huì)調(diào)用__init__構(gòu)造方法,每當(dāng)實(shí)例空間被收回執(zhí)行__del__方法。
>>> x.__class__
<class trac.wrapper at 0x28503f8c>
>>> x.__class__.__name__
'wrapper'
每個(gè)實(shí)例都有內(nèi)置的__class__屬性,引用了它所繼承的類(lèi),而每個(gè)類(lèi)都有__name__屬性,用用了首行中的變量名,所以self.__class__.__name__
是取出實(shí)例的類(lèi)的名稱(chēng)
四、命名空間:完整的內(nèi)容
點(diǎn)號(hào)和無(wú)點(diǎn)號(hào)的變量,會(huì)用不同的方式處理,而有些作用域是用于對(duì)對(duì)象命名空間做初始設(shè)定的。
*無(wú)點(diǎn)號(hào)運(yùn)算的變量名(例如,X)與作用域相對(duì)應(yīng)
*點(diǎn)號(hào)的屬性名(如object.X)使用的是對(duì)象的命名空間。
*有些作用域會(huì)對(duì)對(duì)象的命名空間進(jìn)行初始化(模塊和類(lèi))
1、簡(jiǎn)單變量名:如果賦值就不是全局變量
無(wú)點(diǎn)號(hào)的簡(jiǎn)單運(yùn)算名遵循函數(shù)LEGB作用域法則:
賦值語(yǔ)句(X=value)
??? 使變量名為本地變量:在當(dāng)前作用域內(nèi),創(chuàng)建或改變變量名X,除非聲明它是全局變量。如在函數(shù)的內(nèi)的賦值語(yǔ)句。
引用(X)
??? 在當(dāng)前作用域內(nèi)搜索變量名X,之后是在任何以及所有的嵌套函數(shù)中,然后是在當(dāng)前的全局作用域中搜索,最后在內(nèi)置作用域中搜索。
2、屬性名稱(chēng):對(duì)象命名空間
點(diǎn)號(hào)的屬性名稱(chēng)指的是特定對(duì)象的屬性,并且遵守模塊和類(lèi)的規(guī)則。就類(lèi)和實(shí)例對(duì)象而言,引用規(guī)則增加了繼承搜索這個(gè)流程。
賦值語(yǔ)句(object.X=value)
??? 在進(jìn)行點(diǎn)號(hào)運(yùn)算的對(duì)象的命名空間內(nèi)創(chuàng)建或修改屬性名X,并沒(méi)有其他作用。繼承樹(shù)的搜索只發(fā)生在屬性引用時(shí),而不是屬性的賦值運(yùn)算時(shí)
引用(object.X)
??? 就基于類(lèi)的對(duì)象而言,會(huì)在對(duì)象內(nèi)搜索屬性名X,然后是其上所有可讀取的類(lèi)(使用繼承搜索流程).對(duì)于不是基于類(lèi)的對(duì)象而言,例如模塊,則是從對(duì)象中直接讀取X(可能是的屬性包括,變量名,函數(shù),類(lèi))。
3、命名空間:賦值將變量名分類(lèi)
在Python中,賦值變量名的場(chǎng)所相當(dāng)重要:這完全決定了變量名所在作用域或?qū)ο蟆R幌聦?shí)例總結(jié)了命名空間的概念。
# vim manynames.py
X=11??? #模塊屬性 全局
def f():
??????? print X? #函數(shù)(本地)作用域內(nèi)沒(méi)有X,嵌套函數(shù)沒(méi)有X變量,當(dāng)前全局作用域(模塊的命名空間內(nèi))有,顯示全局
def g():
??????? X=22? #定義本地作用域變量X
??????? print X #搜索函數(shù)(本地)作用域內(nèi)變量X,有打印
class C:
??????? X=33 ??? #定義的類(lèi)屬性,類(lèi)的命名空間
??????? def m(self):
??????????????? X=44??? #貌似在這里沒(méi)有什么意義
??????????????? self.X=55 #定義類(lèi)實(shí)例的屬性,實(shí)例的命名空間
if __name__=='__main__':
??????? print X??? #打印模塊屬性 結(jié)果11
??????? f()??? #調(diào)用f(),f()返回模塊全局變量的X 11
??????? g()??? #調(diào)用g(),g()返回函數(shù)內(nèi)局部變量X 22
??????? print X #打印 模塊全局變量的里變量,模塊的屬性 11
??????? obj=C()??? #調(diào)用類(lèi)的方法產(chǎn)生實(shí)例
??????? print obj.X #打印實(shí)例的屬性X X繼承類(lèi)的屬性,所以為33
??????? obj.m() #實(shí)例調(diào)用類(lèi)的m方法,
??????? print obj.X #顯示這個(gè)X屬性 因?yàn)樯弦徊絤方法設(shè)置了實(shí)例的屬性X,為55
# python manynames.py
11
11
22
11
33
55
作用域總是由源代碼中賦值語(yǔ)句的位置來(lái)決定,而且絕不會(huì)受到其導(dǎo)入關(guān)系的影響。屬性就像是變量,在賦值之后才會(huì)存在。而不是在賦值前。通常情況下,創(chuàng)建實(shí)例屬性的方法是在類(lèi)的__init__構(gòu)造器方法內(nèi)賦值。通常說(shuō)來(lái),在腳本內(nèi)不應(yīng)該讓每個(gè)變量使用相同的命變量名。
4、命名空間字典
模塊的命名空間實(shí)際上是以字典的形式實(shí)現(xiàn)的,并且可以由內(nèi)置屬性__dict__顯示這一點(diǎn)。類(lèi)和實(shí)例對(duì)象也是如此:屬性點(diǎn)號(hào)運(yùn)算其內(nèi)部就是字典的索引運(yùn)算,而屬性繼承其實(shí)就是搜索鏈接的字典而已。實(shí)際上,實(shí)例和類(lèi)對(duì)象就是Python中帶有鏈接的字典而已,
>>> class Super():
...???? def hello(self):
...???????????? self.data1='diege'
...
>>> class Sub(Super):
...???? def hola(self):
...???????????? self.data2='eggs'
...
制作子類(lèi)的實(shí)例時(shí),該實(shí)例一開(kāi)始會(huì)是空的命名空間字典,但是有鏈接會(huì)指向它的類(lèi),讓繼承搜索能順著尋找。實(shí)際上,繼承樹(shù)可在特殊的屬性中看到,你可以進(jìn)行查看。實(shí)例中有個(gè)__class__屬性鏈接到了它的類(lèi),而類(lèi)有個(gè)__base__屬性。就是元組,其中包含了通往更高的超類(lèi)的連接。
>>> X=Sub()?????????????????????
>>> X.__dict__
{}
>>> X.__class__
<class __main__.Sub at 0x2850353c>
>>> Y=Super()
>>> Y.__dict__
{}
>>> Y.__class__
<class __main__.Super at 0x285034ac>
>>> Sub.__bases__
(<class __main__.Super at 0x285034ac>,)
>>> Super.__bases__???
()
當(dāng)類(lèi)為self屬性賦值時(shí),會(huì)填入實(shí)例對(duì)象。也就是說(shuō),屬性最后會(huì)位于實(shí)例的屬性命名空間字典內(nèi),而不是類(lèi)的。實(shí)例對(duì)象的命名空間保存了數(shù)據(jù),會(huì)隨實(shí)例的不同而不同,而self正是進(jìn)入其命名空間的鉤子。
>>> Y=Sub()
>>> X.hello()
>>> X.__dict__
{'data1': 'diege'}
>>> X.hola()???
>>> X.__dict__
{'data1': 'diege', 'data2': 'eggs'}
>>> Sub.__dict__
{'__module__': '__main__', '__doc__': None, 'hola': <function hola at 0x284954c4>}
>>> Super.__dict__
{'__module__': '__main__', 'hello': <function hello at 0x28495f0c>, '__doc__': None}
>>> Sub.__dict__.keys(),Super.__dict__.keys()
(['__module__', '__doc__', 'hola'], ['__module__', 'hello', '__doc__'])
>>> Y.__dict__
{}
Y是這個(gè)類(lèi)的第2個(gè)實(shí)例。即時(shí)X的字典已由方法內(nèi)的賦值語(yǔ)句做了填充,Y還是空的命名空間字典。每個(gè)實(shí)例都有獨(dú)立的命名空間字典,一開(kāi)始是空的,可以記錄和相同類(lèi)的其他實(shí)例命名空間字典中屬性,完全不同的屬性。
因?yàn)閷傩詫?shí)際上是python的字典鍵,其實(shí)有兩種方式可以讀取并對(duì)其進(jìn)行賦值:通過(guò)點(diǎn)號(hào)運(yùn)算,或通過(guò)鍵索引運(yùn)算。
>>> X.data1,X.__dict__['data1']
('diege', 'diege')
>>> X.data3='lily'
>>> X.__dict__????????????????
{'data1': 'diege', 'data3': 'lily', 'data2': 'eggs'}
>>> dir(X)
['__doc__', '__module__', 'data1', 'data2', 'data3', 'hello', 'hola']
>>> dir(Sub)
['__doc__', '__module__', 'hello', 'hola']
>>> dir(Super)
['__doc__', '__module__', 'hello']
對(duì)實(shí)例賦值,只影響實(shí)例,不會(huì)影響實(shí)例的類(lèi)和超類(lèi)
5、命名空間連接
__class__和__bases__這些屬性可以在程序代碼內(nèi)查看繼承層次。可以用他來(lái)顯示類(lèi)樹(shù)
6、一個(gè)較復(fù)雜的例子
vim person.py
class GenericDisplay:
??????? def gatherAttrs(self):
??????????????? attrs='\n'
??????????????? for key in self.__dict__:
??????????????????????? attrs+='\t%s=%s\n' % (key,self.__dict__[key])
??????????????? return attrs
??????? def __str__(self):
??????????????? return '<%s:%s>' % (self.__class__.__name__,self.gatherAttrs())
class Person(GenericDisplay):
??????? def __init__(self,name,age):
??????????????? self.name=name
??????????????? self.age=age
??????? def lastName(self):
??????????????? return self.name.split()[-1]
??????? def birthDay(self):
??????????????? self.age+=1
class Employee(Person):
??????? def __init__(self,name,age,job=None,pay=0):
??????????????? Person.__init__(self,name,age)
??????????????? self.job=job
??????????????? self.pay=pay
??????? def birthDay(self):
??????????????? self.age+=2
??????? def giveRaise(self,percent):
??????????????? self.pay*=(1.0+percent)
if __name__=='__main__':
??????? diege=Person('diege wang',18)
??????? print diege
??????? print diege.lastName()
??????? diege.birthDay()
??? ??? print diege
??????? sue=Employee('Sue Jones',44,job='dev',pay=1000)
??????? print sue
??????? print sue.lastName()
??????? sue.birthDay()
??????? sue.giveRaise(.10)
??????? print sue
# python person.py
<Person:
??????? age=18
??????? name=diege wang
>
wang
<Employee:
??????? job=dev
??????? pay=1000
??????? age=44
??????? name=Sue Jones
>
Jones
<Employee:
??????? job=dev
??????? pay=1100.0
??????? age=46
??????? name=Sue Jones
>
可以作為模塊導(dǎo)入測(cè)試
>>> from person import Person
>>> dir(Person)
['__doc__', '__init__', '__module__', '__str__', 'birthDay', 'gatherAttrs', 'lastName']
>>> diege.__dict__
>>> diege=Person('diege wang',18)
{'age': 18, 'name': 'diege wang'}
>>> print diege
<Person:
??????? age=18
??????? name=diege wang
>
>>> print diege.lastName()
wang
>>> diege.age
18
>>> diege.birthDay()
>>> diege.age
19
轉(zhuǎn)載于:https://blog.51cto.com/ipseek/800522
總結(jié)
以上是生活随笔為你收集整理的Python学习笔记整理(十五)类的编写细节的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java中Date及Timestamp时
- 下一篇: catch and batch