現(xiàn)在我們需要再次闡述 Python 中綁定(binding)的概念,它主要與方法調(diào)用相關(guān)連。
我們先來回顧一下與方法相關(guān)的知識(shí):
24.1.1 核心筆記:self 是什么?首先,方法僅僅是類內(nèi)部定義的函數(shù)。(這意味著方法是類屬性而不是實(shí)例屬性)。
其次,方法只有在其所屬的類擁有實(shí)例時(shí),才能被調(diào)用。當(dāng)存在一個(gè)實(shí)例時(shí),方法才被認(rèn)為是綁定到那個(gè)實(shí)例了。沒有實(shí)例時(shí)方法就是未綁定的。
最后,任何一個(gè)方法定義中的第一個(gè)參數(shù)都是變量 self,它表示調(diào)用此方法的實(shí)例對(duì)象。
self 變量用于在類實(shí)例方法中引用方法所綁定的實(shí)例。因?yàn)榉椒ǖ膶?shí)例在任何方法調(diào)用中總是作為第一個(gè)參數(shù)傳遞的,self 被選中用來代表實(shí)例。你必須在方法聲明中放上 self(你可能已經(jīng)注意到了這點(diǎn)),但可以在方法中不使用實(shí)例(self)。
如果你的方法中沒有用到 self , 那么請(qǐng)考慮創(chuàng)建一個(gè)常規(guī)函數(shù),除非你有特別的原因。畢竟,你的方法代碼沒有使用實(shí)例,沒有與類關(guān)聯(lián)其功能,這使得它看起來更像一個(gè)常規(guī)函數(shù)。在其它面向?qū)ο笳Z(yǔ)言中,self 可能被稱為 this。
24.1.2 調(diào)用綁定方法方法,不管綁定與否,都是由相同的代碼組成的。唯一的不同在于是否存在一個(gè)實(shí)例可以調(diào)用此方法。在很多情況下,程序員調(diào)用的都是一個(gè)綁定的方法。假定現(xiàn)在有一個(gè) MyClass 類和此類的一個(gè)實(shí)例 mc,而你想調(diào)用 MyClass.foo()方法。因?yàn)橐呀?jīng)有一個(gè)實(shí)例,你只需要調(diào)用 mc.foo()就可以。記得 self 在每一個(gè)方法聲明中都是作為第一個(gè)參數(shù)傳遞的。當(dāng)你在實(shí)例中調(diào)用一個(gè)綁定的方法時(shí),self 不需要明確地傳入了。這算是"必須聲明 self 作為第一個(gè)參數(shù)"對(duì)你的報(bào)酬。當(dāng)你還沒有一個(gè)實(shí)例并且需要調(diào)用一個(gè)非綁定方法的時(shí)候你必須傳遞 self 參數(shù)。
24.1.2 調(diào)用未綁定方法調(diào)用非綁定方法并不經(jīng)常用到。需要調(diào)用一個(gè)還沒有任何實(shí)例的類中的方法的一個(gè)主要的場(chǎng)景是:你在派生一個(gè)子類,而且你要覆蓋父類的方法,這時(shí)你需要調(diào)用那個(gè)父類中想要覆蓋掉的構(gòu)造方法。這里是一個(gè)本章前面介紹過的例子:
class EmplAddrBookEntry(AddrBookEntry):
'Employee Address Book Entry class' # 員工地址記錄條目
def __init__(self, nm, ph, em):
AddrBookEntry.__init__(self, nm, ph)
self.empid = id
self.email = em
EmplAddrBookEntry 是 AddrBookEntry 的子類,我們重載了構(gòu)造器__init__()
。我們想盡可能多地重用代碼, 而不是去從父類構(gòu)造器中剪切,粘貼代碼。這樣做還可以避免 BUG 傳播,因?yàn)槿魏涡迯?fù)都可以傳遞給子類。這正是我們想要的 — 沒有必要一行一行地復(fù)制代碼。只需要能夠調(diào)用父類的構(gòu)造器即可,但該怎么做呢?
我們?cè)谶\(yùn)行時(shí)沒有 AddrBookEntry 的實(shí)例。那么我們有什么呢?我們有一個(gè) EmplAddrBookEntry的實(shí)例,它與 AddrBookEntry 是那樣地相似,我們難道不能用它代替呢?當(dāng)然可以!
當(dāng)一個(gè) EmplAddrBookEntry 被實(shí)例化,并且調(diào)用__init__()
時(shí),其與 AddrBookEntry 的實(shí)例只有很少的差別,主要是因?yàn)槲覀冞€沒有機(jī)會(huì)來自定義我們的 EmplAddrBookEntry 實(shí)例,以使它與AddrBookEntry 不同。
調(diào)用非綁定方法的最佳地方
,我們將在子類構(gòu)造器中調(diào)用父類的構(gòu)造器并且明確地傳遞(父類)構(gòu)造器所需要的 self 參數(shù)(因?yàn)槲覀儧]有一個(gè)父類的實(shí)例)。子類中__init__()
的第一行就是對(duì)父類__init__()
的調(diào)用。我們通過父類名來調(diào)用它,并且傳遞給它 self 和其他所需要的參數(shù)。一旦調(diào)用返回,我們就能定義那些與父類不同的僅存在我們的(子)類中的(實(shí)例)定制。
靜態(tài)方法是類中的函數(shù),不需要實(shí)例。靜態(tài)方法主要是用來存放邏輯性的代碼,主要是一些邏輯屬于類,但是和類本身沒有交互,即在靜態(tài)方法中,不會(huì)涉及到類中的方法和屬性的操作??梢岳斫鉃閷㈧o態(tài)方法存在此類的名稱空間中。事實(shí)上,在python引入靜態(tài)方法之前,通常是在全局名稱空間中創(chuàng)建函數(shù)。
示例,我想定義一個(gè)關(guān)于時(shí)間操作的類,其中有一個(gè)獲得當(dāng)前時(shí)間的函數(shù)
import time
class TimeTest(object):
def __init__(self, hour, minute, second):
self.hour = hour
self.minute = minute
self.second = second
@staticmethod
def showTime():
return time.strftime("%H:%M:%S", time.localtime())
print(TimeTest.showTime())
t = TimeTest(2, 10, 10)
nowTime = t.showTime()
print(nowTime)
如上,使用靜態(tài)函數(shù),既可以將獲得時(shí)間的函數(shù)功能與實(shí)例解綁,我想獲得當(dāng)前時(shí)間的字符串時(shí),并不一定需要實(shí)例化對(duì)象,此時(shí)更像是一種名稱空間。我們可以在類外面寫一個(gè)簡(jiǎn)單的方法來做這些,但是這樣做就擴(kuò)散了類代碼的關(guān)系到類定義的外面,這樣寫就會(huì)導(dǎo)致以后代碼維護(hù)的困難。
靜態(tài)函數(shù)可以通過類名以及實(shí)例兩種方法調(diào)用!
24.2.2 類方法類方法是將類本身作為對(duì)象進(jìn)行操作的方法。通常的方法需要一個(gè)實(shí)例(self)作為第一個(gè)參數(shù),并且對(duì)于(綁定的)方法調(diào)用來說,self 是自動(dòng)傳遞給這個(gè)方法的。而對(duì)于類方法而言,需要類而不是實(shí)例作為第一個(gè)參數(shù),它是由解釋器傳給方法。類不需要特別地命名, 類似 self,不過很多人使用 cls 作為變量名字。
他和靜態(tài)方法的區(qū)別在于:不管這個(gè)方式是從實(shí)例調(diào)用還是從類調(diào)用,它都用第一個(gè)參數(shù)把類傳遞過來。
示例:做一個(gè)顏色的動(dòng)態(tài)匹配
class ColorTest(object):
color = "color"
@classmethod
def value(cls):
return cls.color
class Red(ColorTest):
color = "red"
class Green(ColorTest):
color = "green"
g = Green()
print g.value()
print Green.value()
24.2.3 staticmethod()和 classmethod()內(nèi)建函數(shù)現(xiàn)在讓我們看一下在經(jīng)典類中創(chuàng)建靜態(tài)方法和類方法的一些例子(你也可以把它們用在新式類中):
class TestStaticMethod:
def foo():
print 'calling static method foo()'
foo = staticmethod(foo)
class TestClassMethod:
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
foo = classmethod(foo)
對(duì)應(yīng)的內(nèi)建函數(shù)被轉(zhuǎn)換成它們相應(yīng)的類型,并且重新賦值給了相同的變量名。如果沒有調(diào)用這兩個(gè)函數(shù),二者都會(huì)在 Python 編譯器中產(chǎn)生錯(cuò)誤,顯示需要帶 self 的常規(guī)方法聲明?,F(xiàn)在, 我們可以通過類或者實(shí)例調(diào)用這些函數(shù)…這沒什么不同:
>>>tsm = TestStaticMethod()
>>>TestStaticMethod.foo()
calling static method foo()
>>>tsm.foo()
calling static method foo()
>>>>>>tcm = TestClassMethod()
>>>TestClassMethod.foo()
calling class method foo()
foo() is part of class: TestClassMethod
>>>tcm.foo()
calling class method foo()
foo() is part of class: TestClassMethod
24.2.4 使用函數(shù)修飾符現(xiàn)在,看到像 `foo=staticmethod(foo)這樣的代碼會(huì)刺激一些程序員。很多人對(duì)這樣一個(gè)沒意義的語(yǔ)法感到心煩,即使 van Rossum 曾指出過,它只是臨時(shí)的,有待社區(qū)對(duì)些語(yǔ)義進(jìn)行處理。你可以用修飾符@把一個(gè)函數(shù)應(yīng)用到另個(gè)函數(shù)對(duì)象上, 而且新函數(shù)對(duì)象依然綁定在原來的變量。我們正是需要它來整理語(yǔ)法。通過使用 decorators,我們可以避免像上面那樣的重新賦值:
class TestStaticMethod:
@staticmethod
def foo():
print 'calling static method foo()'
class TestClassMethod:
@classmethod
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
24.3 組合一個(gè)類被定義后,目標(biāo)就是要把它當(dāng)成一個(gè)模塊來使用,并把這些對(duì)象嵌入到你的代碼中去,同其它數(shù)據(jù)類型及邏輯執(zhí)行流混合使用。有兩種方法可以在你的代碼中利用類。
第一種是組合(composition)。就是讓不同的類混合并加入到其它類中,來增加功能和代碼重用性。你可以在一個(gè)大點(diǎn)的類中創(chuàng)建你自已的類的實(shí)例,實(shí)現(xiàn)一些其它屬性和方法來增強(qiáng)對(duì)原來的類對(duì)象。另一種方法是通過派生,我們將在下一節(jié)中討論它。
舉例來說,讓我們想象一個(gè)對(duì)本章一開始創(chuàng)建的地址本類的加強(qiáng)性設(shè)計(jì)。如果在設(shè)計(jì)的過程中,為 names,addresses 等等創(chuàng)建了單獨(dú)的類。那么最后我們可能想把這些工作集成到 AddrBookEntry類中去,而不是重新設(shè)計(jì)每一個(gè)需要的類。這樣就節(jié)省了時(shí)間和精力,而且最后的結(jié)果是容易維護(hù)的代碼 — 一塊代碼中的 bugs 被修正,將反映到整個(gè)應(yīng)用中。這樣的類可能包含一個(gè) Name 實(shí)例,以及其它的像 StreetAddress, Phone ( home, work,telefacsimile, pager, mobile, 等等),Email (home, work, 等等。),還可能需要一些 Date 實(shí)(birthday,wedding,anniversary,等等)。下面是一個(gè)簡(jiǎn)單的例子:
class NewAddrBookEntry(object): # class definition 類定義
'new address book entry class'
def __init__(self, nm, ph): # define constructor 定義構(gòu)造器
self.name = Name(nm) # create Name instance 創(chuàng)建 Name 實(shí)例
self.phone = Phone(ph) # create Phone instance 創(chuàng)建 Phone 實(shí)例
print 'Created instance for:', self.name
NewAddrBookEntry 類由它自身和其它類組合而成。這就在一個(gè)類和其它組成類之間定義了一種“has-a / 有一個(gè)”的關(guān)系。比如,我們的 NewAddrBookEntry 類“有一個(gè)” Name 類實(shí)例和一個(gè) Phone實(shí)例。創(chuàng)建復(fù)合對(duì)象就可以實(shí)現(xiàn)這些附加的功能,并且很有意義,因?yàn)檫@些類都不相同。每一個(gè)類管理它們自己的名字空間和行為。
不過當(dāng)對(duì)象之間有更接近的關(guān)系時(shí),派生的概念可能對(duì)你的應(yīng)用程序來說更有意義,特別是當(dāng)你需要一些相似的對(duì)象,但卻有少許不同功能的時(shí)候。
24.4 子類和派生當(dāng)類之間有顯著的不同,并且(較小的類)是較大的類所需要的組件時(shí),組合表現(xiàn)得很好,但當(dāng)你設(shè)計(jì)“相同的類但有一些不同的功能”時(shí),派生就是一個(gè)更加合理的選擇了
OOP 的更強(qiáng)大方面之一是能夠使用一個(gè)已經(jīng)定義好的類,擴(kuò)展它或者對(duì)其進(jìn)行修改,而不會(huì)影響系統(tǒng)中使用現(xiàn)存類的其它代碼片段。
OOD允許類特征在子孫類或子類中進(jìn)行繼承。這些子類從基類(或稱祖先類,超類)繼承它們的核心屬性。而且,這些派生可能會(huì)擴(kuò)展到多代。在一個(gè)層次的派生關(guān)系中的相關(guān)類(或者是在類樹圖中垂直相鄰)是父類和子類關(guān)系。從同一個(gè)父類派生出來的這些類(或者是在類樹圖中水平相鄰)是同胞關(guān)系。父類和所有高層類都被認(rèn)為是祖先。
使用前一節(jié)中的例子,如果我們必須創(chuàng)建不同類型的地址本。即,不僅僅是創(chuàng)建地址本的多個(gè)實(shí)例,在這種情況下,所有對(duì)象幾乎是相同的。如果我們希望 EmplAddrBookEntry 類中包含更多與工作有關(guān)的屬性,如員工 ID 和 e-mail 地址?這跟 PersonalAddrBookEntry 類不同,它包含更多基于家庭的信息,比如家庭地址,關(guān)系,生日等等。
兩種情況下,我們都不想到從頭開始設(shè)計(jì)這些類,因?yàn)檫@樣做會(huì)重復(fù)創(chuàng)建通用的 AddressBook類時(shí)的操作。包含 AddressBook 類所有的特征和特性并加入需要的定制特性不是很好嗎?這就是類派生的動(dòng)機(jī)和要求。
24.4.1 創(chuàng)建子類創(chuàng)建子類的語(yǔ)法看起來與普通(新式)類沒有區(qū)別,一個(gè)類名,后跟一個(gè)或多個(gè)需要從其中派生的父類:
class SubClassName (ParentClass1[, ParentClass2, ...]):
'optional class documentation string'
class_suite
如果你的類沒有從任何祖先類派生,可以使用 object 作為父類的名字。經(jīng)典類的聲明唯一區(qū)別在于沒有從祖先類派生–此時(shí),沒有圓括號(hào):
class ClassicClassWithoutSuperclasses:
pass
至此,我們已經(jīng)看到了一些類和子類的例子,下面還有一個(gè)簡(jiǎn)單的例子:
class Parent(object): # define parent class 定義父類
def parentMethod(self):
print 'calling parent method'
class Child(Parent): # define child class 定義子類
def childMethod(self):
print 'calling child method'
>>>p = Parent() # instance of parent 父類的實(shí)例
>>>p.parentMethod()
calling parent method
>>>>>>c = Child() # instance of child 子類的實(shí)例
>>>c.childMethod()# child calls its method 子類調(diào)用它的方法
calling child method
>>>c.parentMethod() # calls parent's method 調(diào)用父類的方法
calling parent method
24.4.2 繼承繼承描述了基類的屬性如何“遺傳”給派生類。一個(gè)子類可以繼承它的基類的任何屬性,不管是數(shù)據(jù)屬性還是方法。舉個(gè)例子如下。P 是一個(gè)沒有屬性的簡(jiǎn)單類。C 從 P 繼承而來(因此是它的子類),也沒有屬性:
class P(object): # parent class 父類
pass
class C(P): # child class 子類
pass
>>>c = C() # instantiate child 實(shí)例化子類
>>>c.__class__ # child "is a" parent 子類“是一個(gè)”父類>>>C.__bases__ # child's parent class(es) 子類的父類
(,)
因?yàn)?P 沒有屬性,C 沒有繼承到什么。下面我們給 P 添加一些屬性:
class P: # parent class 父類
'P class'
def __init__(self):
print 'created an instance of', self.__class__.__name__
class C(P): # child class 子類
pass
現(xiàn)在所創(chuàng)建的 P 有文檔字符串(__doc__
)和構(gòu)造器,當(dāng)我們實(shí)例化 P 時(shí)它被執(zhí)行,如下面的交互會(huì)話所示:
>>>p = P() # parent instance 父類實(shí)例
created an instance of P
>>>p.__class__ # class that created us 顯示 p 所屬的類名>>>P.__bases__ # parent's parent class(es) 父類的父類
(,)
>>>P.__doc__ # parent's doc string 父類的文檔字符串
'P class'
“created an instance”是由__init__()
直接輸出的。我們也可顯示更多關(guān)于父類的信息。我們現(xiàn)在來實(shí)例化 C,展示__init__()
(構(gòu)造)方法在執(zhí)行過程中是如何繼承的:
>>>c = C() # child instance 子類實(shí)例
created an instance of C
>>>c.__class__ # class that created us 顯示 c 所屬的類名>>>C.__bases__ # child's parent class(es) 子類的父類
(,)
>>>C.__doc__ # child's doc string 子類的文檔字符串
C 沒有聲明__init__()
方法,然而在類 C 的實(shí)例 c 被創(chuàng)建時(shí),還是會(huì)有輸出信息。原因在于 C 繼承了 P 的__init__()
。__bases__
元組列出了其父類 P。需要注意的是文檔字符串對(duì)類,函數(shù)/方法,還有模塊來說都是唯一的,所以特殊屬性__doc__
不會(huì)從基類中繼承過來。
__bases__
類屬性__bases__
類屬性對(duì)任何(子)類,它是一個(gè)包含其所有父類(parent)的集合的元組,那些沒有父類的類,它們的__bases__
屬性為空。下面我們看一下如何使用__bases__
的。
>>>class A(object): pass # define class A 定義類 A
...
>>>class B(A): pass # subclass of A A 的子類
...
>>>class C(B): pass # subclass of B (and indirectly, A) B 的子類(A 的間接子類)
...
>>>class D(A, B): pass # subclass of A and B A,B 的子類
...
>>>A.__bases__
(,)
>>>C.__bases__
(,)
>>>D.__bases__
(,)
在上面的例子中,盡管 C 是 A 和 B 的子類(通過 B 傳遞繼承關(guān)系),但 C 的父類是 B,這從它的聲明中可以看出,所以,只有 B 會(huì)在 C.__bases__
中顯示出來。另一方面,D 是從兩個(gè)類 A 和 B 中繼承而來的。
我們?cè)?P 中再寫一個(gè)函數(shù),然后在其子類中對(duì)它進(jìn)行覆蓋。
class P(object):
def foo(self):
print 'Hi, I am P-foo()'
>>>p = P()
>>>p.foo()
Hi, I am P-foo()
現(xiàn)在來創(chuàng)建子類 C,從父類 P 派生:
class C(P):
def foo(self):
print 'Hi, I am C-foo()'
>>>c = C()
>>>c.foo()
Hi, I am C-foo()
>>>p.foo()
Hi, I am P-foo()
盡管 C 繼承了 P 的 foo()方法,但因?yàn)?C 定義了它自已的 foo()方法,所以 P 中的 foo() 方法被覆蓋。覆蓋方法的原因之一是,你的子類可能需要這個(gè)方法具有特定或不同的功能。
所以,你接下來的問題肯定是:“我還能否調(diào)用那個(gè)被我覆蓋的基類方法呢?”
答案是肯定的,但是這時(shí)就需要你去調(diào)用一個(gè)未綁定的基類方法,明確給出子類的實(shí)例,例如下邊:
>>>P.foo(c)
Hi, I am P-foo()
注意,我們上面已經(jīng)有了一個(gè) P 的實(shí)例 p,但上面的這個(gè)例子并沒有用它。我們不需要 P 的實(shí)例調(diào)用 P 的方法,因?yàn)橐呀?jīng)有一個(gè) P 的子類的實(shí)例 c 可用(但是仍可以傳實(shí)例p)。典型情況下,你不會(huì)以這種方式調(diào)用父類方法,你會(huì)在子類的重寫方法里顯式地調(diào)用基類方法。
class C(P):
def foo(self):
P.foo(self)
print 'Hi, I am C-foo()'
注意,在這個(gè)(未綁定)方法調(diào)用中我們顯式地傳遞了 self. 一個(gè)更好的辦法是使用 super()(super下文介紹)內(nèi)建方法:
class C(P):
def foo(self):
super(C, self).foo()
print 'Hi, I am C-foo()'
super()不但能找到基類方法,而且還為我們傳進(jìn) self,這樣我們就不需要做這些事了。現(xiàn)在我們只要調(diào)用子類的方法,它會(huì)幫你完成一切:
>>>c = C()
>>>c.foo()
Hi, I am P-foo()
Hi, I am C-foo()
24.4.5 核心筆記:重寫__init__
不會(huì)自動(dòng)調(diào)用基類的__init__
類似于上面的覆蓋非特殊方法,當(dāng)從一個(gè)帶構(gòu)造器__init()__
的類派生,如果你不去覆蓋__init__()
,它將會(huì)被繼承并自動(dòng)調(diào)用。但如果你在子類中覆蓋了__init__()
,子類被實(shí)例化時(shí),基類的__init__()
就不會(huì)被自動(dòng)調(diào)用。
class P(object):
def __init__(self):
print "calling P's constructor"
class C(P):
def __init__(self):
print "calling C's constructor"
>>>c = C()
calling C's constructor
如果你還想調(diào)用基類的__init__()
,你需要像上邊我們剛說的那樣,明確指出,使用一個(gè)子類的實(shí)例去調(diào)用基類(未綁定)方法。相應(yīng)地更新類 C,會(huì)出現(xiàn)下面預(yù)期的執(zhí)行結(jié)果:
class C(P):
def __init__(self):
P.__init__(self)
print "calling C's constructor"
>>>c = C()
calling P's constructor
calling C's constructor
上邊的例子中,子類的__init__()
方法首先調(diào)用了基類的的__init__()
方法。這是相當(dāng)普遍(不是強(qiáng)制)的做法,用來設(shè)置初始化基類,然后可以執(zhí)行子類內(nèi)部的設(shè)置。這個(gè)規(guī)則之所以有意義的,原因是,你希望被繼承的類的對(duì)象在子類構(gòu)造器運(yùn)行前能夠很好地被初始化或作好準(zhǔn)備工作,因?yàn)樗?子類)可能需要或設(shè)置繼承屬性。
Python 使用基類名來調(diào)用類方法,對(duì)應(yīng)在 JAVA 中,是用關(guān)鍵字 super 來實(shí)現(xiàn)的,這就是 super()內(nèi)建函數(shù)引入到 Python 中的原因,這樣你就可以“依葫蘆畫瓢”了:
class C(P):
def __init__(self):
super(C, self).__init__()
print "calling C's constructor"
使用 **super()**的漂亮之處在于,你不需要明確給出任何基類名字…“跑腿事兒”,它幫你干了!使用 super()的重點(diǎn),是你不需要明確提供父類。這意味著如果你改變了類繼承關(guān)系,你只需要改一行代碼(class 語(yǔ)句本身)而不必在大量代碼中去查找所有被修改的那個(gè)類的名字。
super語(yǔ)法格式:
super([type[, object-or-type]])
函數(shù)描述:
返回一個(gè)代理對(duì)象,它會(huì)將方法調(diào)用委托給 type 的父類或兄弟類。參數(shù)說明:
type —— 類,可選參數(shù)。
object-or-type —— 對(duì)象或類,一般是 self,可選參數(shù)。返回值:
super object —— 代理對(duì)象。super 是一個(gè)繼承自 object 的類,調(diào)用 super() 函數(shù)其實(shí)就是 super 類的實(shí)例化。
根據(jù)官方文檔的解釋 super() 函數(shù)返回的對(duì)象 —— super object,就是一個(gè)代理對(duì)象(筆者也不太理解代理對(duì)象的含義)。
當(dāng)從多個(gè)基類繼承時(shí),super()會(huì)按照“從左至右”順序繼承,后文會(huì)詳細(xì)說明
class P(object):
def foo(self):
print 'Hi, I am P-foo()'
class Q(object):
def foo(self):
print 'Hi, I am Q foo()'
class C(Q,P):
def foo(self):
#P.foo(self)
super(C,self).foo()
print 'Hi, I am C-foo()'
c=C()
c.foo()
>>>Hi, I am Q foo()
Hi, I am C-foo()
super詳解
24.4.6 從標(biāo)準(zhǔn)類型派生經(jīng)典類中,一個(gè)大的問題是,不能對(duì)標(biāo)準(zhǔn)類型進(jìn)行子類化。幸運(yùn)的是,在 2.2 以后的版本中,隨著類型(types)和類(class)的統(tǒng)一和新式類的引入, 這一點(diǎn)已經(jīng)被修正。下面,介紹兩個(gè)子類化 Python 類型的相關(guān)例子,其中一個(gè)是可變類型,另一個(gè)是不可變類型。
24.4.6.1 不可變類型的例子假定你想在金融應(yīng)用中,應(yīng)用一個(gè)處理浮點(diǎn)數(shù)的子類。每次你得到一個(gè)貸幣值(浮點(diǎn)數(shù)給出的),你都需要通過四舍五入,變?yōu)閹晌恍?shù)位的數(shù)值。(當(dāng)然,Decimal 類比起標(biāo)準(zhǔn)浮點(diǎn)類型來說是個(gè)用來精確保存浮點(diǎn)值的更佳方案,但你還是需要[有時(shí)候]對(duì)其進(jìn)行舍入操作!)你的類開始可以
這樣寫:
class RoundFloat(float):
def __new__(cls, val):
return float.__new__(cls, round(val, 2))
我們覆蓋了__new__()
特殊方法來定制我們的對(duì)象,使之和標(biāo)準(zhǔn) Python 浮點(diǎn)數(shù)(float)有一些區(qū)別:
我們使用 round()內(nèi)建函數(shù)對(duì)原浮點(diǎn)數(shù)進(jìn)行舍入操作,然后實(shí)例化我們的 float,RoundFloat。
我們是通過調(diào)用父類的構(gòu)造器來創(chuàng)建真實(shí)的對(duì)象的,float.__new__()
。注意,所有的__new()__
方法都是類方法,我們要顯式傳入類傳為第一個(gè)參數(shù),這類似于常見的方法如__init__()
中需要的 self。現(xiàn)在的例子還非常簡(jiǎn)單,比如,我們知道有一個(gè) float,我們僅僅是從一種類型中派生而來等.通常情況下,最好是使用 super()內(nèi)建函數(shù)去捕獲對(duì)應(yīng)的父類以調(diào)用它的__new()__
方法,下面,對(duì)它進(jìn)行這方面的修改:
class RoundFloat(float):
def __new__(cls, val):
return super(RoundFloat, cls).__new__(cls, round(val, 2))
這個(gè)例子還遠(yuǎn)不夠完整,所以,請(qǐng)留意本章我們將使它有更好的表現(xiàn)。下面是一些樣例輸出:
>>>RoundFloat(1.5955)
1.6
>>>RoundFloat(1.5945)
1.59
>>>RoundFloat(-1.9955)
-2
24.4.6.2 可變類型的例子子類化一個(gè)可變類型,你可能不需要使用__new__()
(或甚至__init__()
),因?yàn)橥ǔTO(shè)置不多。一般情況下,你所繼承到的類型的默認(rèn)行為就是你想要的。下例中,我們簡(jiǎn)單地創(chuàng)建一個(gè)新的字典類型,它的 keys()方法會(huì)自動(dòng)排序結(jié)果:
class SortedKeyDict(dict):
def keys(self):
return sorted(super( SortedKeyDict, self).keys())
回憶一下,字典(dictionary)可以由 dict(),dict(mapping),dict(sequence_of_2_tuples),或者 dict(**kwargs)來創(chuàng)建,看看下面使用新類的例子:
d = SortedKeyDict((('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2)))
print 'By iterator:'.ljust(12), [key for key in d]
print 'By keys():'.ljust(12), d.keys()
把上面的代碼全部加到一個(gè)腳本中,然后運(yùn)行,可以得到下面的輸出:
By iterator: ['zheng-cai', 'xin-yi', 'hui-jun']
By keys(): ['xin-yi', 'hui-jun', 'zheng-cai']
在上例中,通過 keys 迭代過程是以散列順序的形式,而使用我們(重寫的)keys()方法則將keys 變?yōu)樽帜概判蚍绞搅?。一定要?jǐn)慎,而且要意識(shí)到你正在干什么。如果你說,“你的方法調(diào)用 super()過于復(fù)雜”,取而代之的是,你更喜歡 keys()簡(jiǎn)簡(jiǎn)單單(也容易理解)…,像這樣:
def keys(self):
return sorted(self.keys())
24.4.7 多重繼承同 C++一樣,Python 允許子類繼承多個(gè)基類。這種特性就是通常所說的多重繼承。概念容易,但最難的工作是,如何正確找到?jīng)]有在當(dāng)前(子)類定義的屬性。當(dāng)使用多重繼承時(shí),有兩個(gè)不同的方面要記住。首先,還是要找到合適的屬性。另一個(gè)就是當(dāng)你重寫方法時(shí),如何調(diào)用對(duì)應(yīng)父類方
法以“發(fā)揮他們的作用”,同時(shí),在子類中處理好自己的義務(wù)。我們將討論兩個(gè)方面,但側(cè)重后者,討論方法解析順序。
精確順序解釋很復(fù)雜,超出了本文的范疇,但你可以去閱讀本節(jié)后面的參考書目提到的有關(guān)內(nèi)容。
這里提一下,新的查詢方法是采用廣度優(yōu)先,而不是深度優(yōu)先。
下面的示例,展示經(jīng)典類和新式類中,方法解釋順序有什么不同。這個(gè)例子將對(duì)兩種類的方案不同處做一展示。腳本由一組父類,一組子類,還有一個(gè)子孫類組成。
class P1: #(object): # parent class 1 父類 1
def foo(self):
print 'called P1-foo()'
class P2: #(object):
def foo(self):
print 'called P2-foo()'
def bar(self):
print 'called P2-bar()'
class C1(P1, P2): # child 1 der. from P1, P2 #子類 1,從 P1,P2 派生
pass
class C2(P1, P2): # child 2 der. from P1, P2 #子類 2,從 P1,P2 派生
def bar(self):
print 'called C2-bar()'
class GC(C1, C2): # define grandchild class #定義子孫類 #從 C1,C2 派生
pass # derived from C1 and C2
我們看到父類,子類及子孫類的關(guān)系。P1 中定義了 foo(),P2 定義了 foo()和 bar(),C2 定義了 bar()。下面舉例說明一下經(jīng)典類和新式類的行為。
24.4.7.2 經(jīng)典類首先來使用經(jīng)典類。通過在交互式解釋器中執(zhí)行上面的聲明,我們可以驗(yàn)證經(jīng)典類使用的解釋順序,深度優(yōu)先,從左至右:
>>>gc = GC()
>>>gc.foo() # GC ==>C1 ==>P1
called P1-foo()
>>>gc.bar() # GC ==>C1 ==>P1 ==>P2
called P2-bar()
當(dāng)調(diào)用 foo()時(shí),它首先在當(dāng)前類(GC)中查找。如果沒找到,就向上查找最親的父類,C1。查找未遂,就繼續(xù)沿樹上訪到父類 P1,foo()被找到。同樣,對(duì) bar()來說,它通過搜索 GC,C1,P1 然后在 P2 中找到。因?yàn)槭褂眠@種解釋順序的緣故,C2.bar()根本就不會(huì)被搜索了?,F(xiàn)在,你可能在想,
"我更愿意調(diào)用 C2 的 bar()方法,因?yàn)樗诶^承樹上和我更親近些,這樣才會(huì)更合適?!痹谶@種情況下,你當(dāng)然還可以使用它,但你必須調(diào)用它的合法的全名,采用典型的非綁定方式去調(diào)用,并且提供一個(gè)合法的實(shí)例:
>>>C2.bar(gc)
called C2-bar()
24.4.7.3 新式類取消類 P1 和類 P2 聲明中的對(duì)(object)的注釋,重新執(zhí)行一下。新式方法的查詢有一些不同:
>>>gc = GC()
>>>gc.foo() # GC ==>C1 ==>C2 ==>P1
called P1-foo()
>>>gc.bar() # GC ==>C1 ==>C2
called C2-bar()
與沿著繼承樹一步一步上溯不同,它首先查找同胞兄弟,采用一種廣度優(yōu)先的方式。當(dāng)查找foo(),它檢查 GC,然后是 C1 和 C2,然后在 P1 中找到。如果 P1 中沒有,查找將會(huì)到達(dá) P2。foo()的底線是,包括經(jīng)典類和新式類都會(huì)在 P1 中找到它,然而它們雖然是同歸,但殊途!然而,bar()的結(jié)果是不同的。它搜索 GC 和 C1,緊接著在 C2 中找到了。這樣,就不會(huì)再繼續(xù)搜索到祖父 P1 和 P2。這種情況下,新的解釋方式更適合那種要求查找 GC 更親近的 bar()的方案。當(dāng)然,如果你還需要調(diào)用上一級(jí),只要按前述方法,使用非綁定的方式去做,即可。
>>>P2.bar(gc)
called P2-bar()
新式類也有一個(gè)__mro__
屬性,告訴你查找順序是怎樣的:
>>>GC.__mro__
(,,,,,)
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧
本文題目:SECTION24面向?qū)ο缶幊蹋ㄈ?創(chuàng)新互聯(lián)
URL網(wǎng)址:http://muchs.cn/article24/eegje.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供域名注冊(cè)、企業(yè)建站、微信小程序、企業(yè)網(wǎng)站制作、網(wǎng)站設(shè)計(jì)公司、網(wǎng)站內(nèi)鏈
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)