SECTION24面向?qū)ο缶幊蹋ㄈ?創(chuàng)新互聯(lián)

面向?qū)ο缶幊?ul>
    • 24.1 再談綁定和方法調(diào)用
        • 24.1.1 核心筆記:self 是什么?
        • 24.1.2 調(diào)用綁定方法
        • 24.1.2 調(diào)用未綁定方法
    • 24.2 靜態(tài)方法和類方法
        • 24.2.1 靜態(tài)方法
        • 24.2.2 類方法
        • 24.2.3 staticmethod()和 classmethod()內(nèi)建函數(shù)
        • 24.2.4 使用函數(shù)修飾符
    • 24.3 組合
    • 24.4 子類和派生
        • 24.4.1 創(chuàng)建子類
        • 24.4.2 繼承
        • 24.4.3 `__bases__`類屬性
        • 24.4.4 通過繼承覆蓋(Overriding)方法
        • 24.4.5 核心筆記:重寫`__init__`不會(huì)自動(dòng)調(diào)用基類的`__init__`
        • 24.4.6 從標(biāo)準(zhǔn)類型派生
          • 24.4.6.1 不可變類型的例子
          • 24.4.6.2 可變類型的例子
        • 24.4.7 多重繼承
          • 24.4.7.1 方法解釋順序(MRO)
          • 24.4.7.2 經(jīng)典類
          • 24.4.7.3 新式類
  • 成都創(chuàng)新互聯(lián)始終堅(jiān)持【策劃先行,效果至上】的經(jīng)營(yíng)理念,通過多達(dá)十余年累計(jì)超上千家客戶的網(wǎng)站建設(shè)總結(jié)了一套系統(tǒng)有效的網(wǎng)絡(luò)營(yíng)銷推廣解決方案,現(xiàn)已廣泛運(yùn)用于各行各業(yè)的客戶,其中包括:成都集裝箱等企業(yè),備受客戶贊美。24.1 再談綁定和方法調(diào)用

    現(xiàn)在我們需要再次闡述 Python 中綁定(binding)的概念,它主要與方法調(diào)用相關(guān)連。
    我們先來回顧一下與方法相關(guān)的知識(shí):

    首先,方法僅僅是類內(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ì)象。

    24.1.1 核心筆記:self 是什么?

    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í)例)定制。

    24.2 靜態(tài)方法和類方法 24.2.1 靜態(tài)方法

    靜態(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ì)從基類中繼承過來。

    24.4.3__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 中繼承而來的。

    24.4.4 通過繼承覆蓋(Overriding)方法

    我們?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è)重后者,討論方法解析順序。

    24.4.7.1 方法解釋順序(MRO)

    精確順序解釋很復(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)

    成都定制網(wǎng)站建設(shè)