面向對象設計與面向對象編程的關(guān)系
面向對象設計(OOD)不會(huì )特別要求面向對象編程語(yǔ)言。事實(shí)上,OOD 可以由純結構化語(yǔ)言來(lái)實(shí)現,比如 C,但如果想要構造具備對象性質(zhì)和特點(diǎn)的數據類(lèi)型,就需要在程序上作更多的努力。當一門(mén)語(yǔ)言?xún)冉?OO 特性,OO 編程開(kāi)發(fā)就會(huì )更加方便高效。另一方面,一門(mén)面向對象的語(yǔ)言不一定會(huì )強制你寫(xiě) OO 方面的程序。例如 C++可以被認為“更好的C”;而 Java,則要求萬(wàn)物皆類(lèi),此外還規定,一個(gè)源文件對應一個(gè)類(lèi)定義。然而,在 Python 中,類(lèi)和 OOP 都不是日常編程所必需的。盡管它從一開(kāi)始設計就是面向對象的,并且結構上支持 OOP,但Python 沒(méi)有限定或要求你在你的應用中寫(xiě) OO 的代碼。OOP 是一門(mén)強大的工具,不管你是準備進(jìn)入,學(xué)習,過(guò)渡,或是轉向 OOP,都可以任意支配??紤]用 OOD 來(lái)工作的一個(gè)最重要的原因,在于它直接提供建模和解決現實(shí)世界問(wèn)題和情形的途徑。
類(lèi)
類(lèi)是一種數據結構,我們可以用它來(lái)定義對象,后者把數據值和行為特性融合在一起。類(lèi)是現實(shí)世界的抽象的實(shí)體以編程形式出現。實(shí)例是這些對象的具體化??梢灶?lèi)比一下,類(lèi)是藍圖或者模型,用來(lái)產(chǎn)生真實(shí)的物體(實(shí)例)。類(lèi)還可以派生出相似但有差異的子類(lèi)。編程中類(lèi)的概念就應用了很多這樣的特征。在 Python 中,類(lèi)聲明與函數聲明很相似,頭一行用一個(gè)相應的關(guān)鍵字,接下來(lái)是一個(gè)作為它的定義的代碼體,如下所示:
二者都允許你在他們的聲明中創(chuàng )建函數,閉包或者內部函數(即函數內的函數),還有在類(lèi)中定義的方法。最大的不同在于你運行函數,而類(lèi)會(huì )創(chuàng )建一個(gè)對象。類(lèi)就像一個(gè) Python 容器類(lèi)型。盡管類(lèi)是對象(在 Python 中,一切皆對象),但正被定義時(shí),它們還不是對象的實(shí)現。
創(chuàng )建類(lèi)
Python 類(lèi)使用 class 關(guān)鍵字來(lái)創(chuàng )建。簡(jiǎn)單的類(lèi)的聲明可以是關(guān)鍵字后緊跟類(lèi)名:
基類(lèi)是一個(gè)或多個(gè)用于繼承的父類(lèi)的集合;類(lèi)體由所有聲明語(yǔ)句,類(lèi)成員定義,數據屬性和函數組成。類(lèi)通常在一個(gè)模塊的頂層進(jìn)行定義,以便類(lèi)實(shí)例能夠在類(lèi)所定義
的源代碼文件中的任何地方被創(chuàng )建。
聲明與定義
對于 Python 函數來(lái)說(shuō),聲明與定義類(lèi)沒(méi)什么區別,因為他們是同時(shí)進(jìn)行的,定義(類(lèi)體)緊跟在聲明(含 class 關(guān)鍵字的頭行[header line])和可選的文檔字符串后面。同時(shí),所有的方法也必須同時(shí)被定義。如果對 OOP 很熟悉,請注意 Python 并不支持純虛函數(像 C++)或者抽象方法(如在 JAVA 中),這些都強制程序員在子類(lèi)中定義方法。作為替代方法,你可以簡(jiǎn)單地在基類(lèi)方法中引發(fā) NotImplementedError 異常,這樣可以獲得類(lèi)似的效果。
類(lèi)屬性
屬性就是屬于另一個(gè)對象的數據或者函數元素,可以通過(guò)我們熟悉的句點(diǎn)屬性標識法來(lái)訪(fǎng)問(wèn)。一些 Python 類(lèi)型比如復數有數據屬性(實(shí)部和虛部),而另外一些,像列表和字典,擁有方法(函數屬性)。
有關(guān)屬性的一個(gè)有趣的地方是,當你正訪(fǎng)問(wèn)一個(gè)屬性時(shí),它同時(shí)也是一個(gè)對象,擁有它自己的屬性,可以訪(fǎng)問(wèn),這導致了一個(gè)屬性鏈,比如,myThing,subThing,subSubThing.等等
類(lèi)的數據屬性
數據屬性?xún)H僅是所定義的類(lèi)的變量。它們可以像任何其它變量一樣在類(lèi)創(chuàng )建后被使用,并且,要么是由類(lèi)中的方法來(lái)更新,要么是在主程序其它什么地方被更新。
這種屬性已為 OO 程序員所熟悉,即靜態(tài)變量,或者是靜態(tài)數據。它們表示這些數據是與它們所屬的類(lèi)對象綁定的,不依賴(lài)于任何類(lèi)實(shí)例。如果你是一位 Java 或 C++程序員,這種類(lèi)型的數據相當于在一個(gè)變量聲明前加上 static 關(guān)鍵字。靜態(tài)成員通常僅用來(lái)跟蹤與類(lèi)相關(guān)的值。
看下面的例子,使用類(lèi)數據屬性(foo):
方法
任何像函數一樣對 myNoActionMethod 自身的調用都將失?。?/p>
甚至由類(lèi)對象調用此方法也失敗了。
綁定(綁定及非綁定方法)
為與 OOP 慣例保持一致,Python 嚴格要求,沒(méi)有實(shí)例,方法是不能被調用的。這種限制即 Python所描述的綁定概念(binding),在此,方法必須綁定(到一個(gè)實(shí)例)才能直接被調用。非綁定的方法可能可以被調用,但實(shí)例對象一定要明確給出,才能確保調用成功。然而,不管是否綁定,方法都是它所在的類(lèi)的固有屬性,即使它們幾乎總是通過(guò)實(shí)例來(lái)調用的。
決定類(lèi)的屬性
要知道一個(gè)類(lèi)有哪些屬性,有兩種方法。最簡(jiǎn)單的是使用 dir()內建函數。另外是通過(guò)訪(fǎng)問(wèn)類(lèi)的字典屬性__dict__,這是所有類(lèi)都具備的特殊屬性之一。
看一下下面的例子:
運行結果:
使用:
從上面可以看到,dir()返回的僅是對象的屬性的一個(gè)名字列表,而__dict__返回的是一個(gè)字典,它的鍵(keys)是屬性名,鍵值(values)是相應的屬性對象的數據值。
結果還顯示了 MyClass 類(lèi)中兩個(gè)熟悉的屬性,showMyVersion 和 myVersion,以及一些新的屬性。這些屬性,__doc__及__module__,是所有類(lèi)都具備的特殊類(lèi)屬性(另外還有__dict__)。。內建的 vars()函數接受類(lèi)對象作為參數,返回類(lèi)的__dict__屬性的內容。
特殊的類(lèi)屬性
對任何類(lèi)C,表顯示了類(lèi)C的所有特殊屬性:
C.__name__ 類(lèi)C的名字(字符串)
C.__doc__ 類(lèi)C的文檔字符串
C.__bases__ 類(lèi)C的所有父類(lèi)構成的元組
C.__dict__ 類(lèi)C的屬性
C.__module__ 類(lèi)C定義所在的模塊(1.5 版本新增)
C.__class__ 實(shí)例C對應的類(lèi)(僅新式類(lèi)中)
實(shí)例
如果說(shuō)類(lèi)是一種數據結構定義類(lèi)型,那么實(shí)例則聲明了一個(gè)這種類(lèi)型的變量。實(shí)例是那些主要用在運行期時(shí)的對象,類(lèi)被實(shí)例化得到實(shí)例,該實(shí)例的類(lèi)型就是這個(gè)被實(shí)例化的類(lèi)。
初始化:通過(guò)調用類(lèi)對象來(lái)創(chuàng )建實(shí)例
Python 的方式更加簡(jiǎn)單。一旦定義了一個(gè)類(lèi),創(chuàng )建實(shí)例比調用一個(gè)函數還容易------不費吹灰之力。實(shí)例化的實(shí)現,可以使用函數操作符,如下示:
>>> class MyClass(object): # define class 定義類(lèi)
pass
>>> mc = MyClass() # instantiate class 初始化類(lèi)
__init__()"構造器"方法
當類(lèi)被調用,實(shí)例化的第一步是創(chuàng )建實(shí)例對象。一旦對象創(chuàng )建了,Python 檢查是否實(shí)現了__init__()方法。默認情況下,如果沒(méi)有定義(或覆蓋)特殊方法__init__(),對實(shí)例不會(huì )施加任何特別的操作.任何所需的特定操作,都需要程序員實(shí)現__init__(),覆蓋它的默認行為。
如果__init__()沒(méi)有實(shí)現,則返回它的對象,實(shí)例化過(guò)程完畢。
如果__init__()已經(jīng)被實(shí)現,那么它將被調用,實(shí)例對象作為第一個(gè)參數(self)被傳遞進(jìn)去,像標準方法調用一樣。調用類(lèi)時(shí),傳進(jìn)的任何參數都交給了__init__()。實(shí)際中,你可以想像成這樣:把創(chuàng )建實(shí)例的調用當成是對構造器的調用。
__new__()“構造器”方法
與__init__()相比,__new__()方法更像一個(gè)真正的構造器。需要一種途徑來(lái)實(shí)例化不可變對象,比如,派生字符串,數字,等等。在這種情況下,解釋器則調用類(lèi)的__new__()方法,一個(gè)靜態(tài)方法,并且傳入的參數是在類(lèi)實(shí)例化操作時(shí)生成的。__new__()會(huì )調用父類(lèi)的__new__()來(lái)創(chuàng )建對象(向上代理)。__new__()必須返回一個(gè)合法的實(shí)例。
__del__()"解構器"方法
同樣,有一個(gè)相應的特殊解構器(destructor)方法名為_(kāi)_del__()。然而,由于 Python 具有垃圾對象回收機制(靠引用計數),這個(gè)函數要直到該實(shí)例對象所有的引用都被清除掉后才會(huì )執行。Python 中的解構器是在實(shí)例釋放前提供特殊處理功能的方法,它們通常沒(méi)有被實(shí)現,因為實(shí)例很少被顯式釋放。
注意:Python 沒(méi)有提供任何內部機制來(lái)跟蹤一個(gè)類(lèi)有多少個(gè)實(shí)例被創(chuàng )建了,或者記錄這些實(shí)例是些什么東西。如果需要這些功能,你可以顯式加入一些代碼到類(lèi)定義或者__init__()和__del__()中去。最好的方式是使用一個(gè)靜態(tài)成員來(lái)記錄實(shí)例的個(gè)數??勘4嫠鼈兊囊脕?lái)跟蹤實(shí)例對象是很危險的,因為你必須合理管理這些引用,不然,你的引用可能沒(méi)辦法釋放(因為還有其它的引用)!看下面一個(gè)例子:
實(shí)例屬性
設置實(shí)例的屬性可以在實(shí)例創(chuàng )建后任意時(shí)間進(jìn)行,也可以在能夠訪(fǎng)問(wèn)實(shí)例的代碼中進(jìn)行。構造器__init()__是設置這些屬性的關(guān)鍵點(diǎn)之一
能夠在“運行時(shí)”創(chuàng )建實(shí)例屬性,是 Python 類(lèi)的優(yōu)秀特性之一,Python 不僅是動(dòng)態(tài)類(lèi)型,而且在運行時(shí),允許這些對象屬性的動(dòng)態(tài)創(chuàng )建。這種特性讓人愛(ài)不釋
手。當然,創(chuàng )建這樣的屬性時(shí),必須謹慎。一個(gè)缺陷是,屬性在條件語(yǔ)句中創(chuàng )建,如果該條件語(yǔ)句塊并未被執行,屬性也就不存在,而你在后面的代碼中試著(zhù)去訪(fǎng)問(wèn)這些屬性,就會(huì )有錯誤發(fā)生。
默認參數提供默認的實(shí)例安裝
在實(shí)際應用中,帶默認參數的__init__()提供一個(gè)有效的方式來(lái)初始化實(shí)例。在很多情況下,默認值表示設置實(shí)例屬性的最常見(jiàn)的情況,如果提供了默認值,我們就沒(méi)必要顯式給構造器傳值了。
>>> sfo = HotelRoomCalc(299)
>>> sfo.calcTotal()
4960.41
>>> sfo.calcTotal(2)
9920.82
>>> sea = HotelRoomCalc(189, 0.086, 0.085)
>>> sea.calcTotal()
3098.47
>>> sea.calcTotal(4)
12393.88
函數所有的靈活性,比如默認參數,也可以應用到方法中去。在實(shí)例化時(shí),可變長(cháng)度參數也是一個(gè)好的特性
__init__()應當返回 None
采用函數操作符調用類(lèi)對象會(huì )創(chuàng )建一個(gè)類(lèi)實(shí)例,也就是說(shuō)這樣一種調用過(guò)程返回的對象就是實(shí)例,下面示例可以看出:
>>> mc = MyClass()
>>> mc
<__main__.MyClass object at 0x0134E610>
如果定義了構造器,它不應當返回任何對象,因為實(shí)例對象是自動(dòng)在實(shí)例化調用后返回的。相應地,__init__()就不應當返回任何對象(應當為 None);否則,就可能出現沖突,因為只能返回實(shí)例。試著(zhù)返回非 None 的任何其它對象都會(huì )導致 TypeError 異常:
>>> mc = MyClass()
initialized
Traceback (most recent call last):
File "<pyshell#86>", line 1, in <module>
mc = MyClass()
TypeError: __init__() should return None
查看實(shí)例屬性
內建函數 dir()可以顯示類(lèi)屬性,同樣還可以打印所有實(shí)例屬性:
與類(lèi)相似,實(shí)例也有一個(gè)__dict__特殊屬性(可以調用 vars()并傳入一個(gè)實(shí)例來(lái)獲?。?,它是實(shí)例屬性構成的一個(gè)字典:
特殊的實(shí)例屬性
實(shí)例僅有兩個(gè)特殊屬性。對于任意對象I:
I.__class__ 實(shí)例化 I 的類(lèi)
I.__dict__ I 的屬性
內建類(lèi)型屬性
內建類(lèi)型也是類(lèi),對內建類(lèi)型也可以使用dir(),與任何其它對象一樣,可以得到一個(gè)包含它屬性名字的列表:
試著(zhù)訪(fǎng)問(wèn)__dict__會(huì )失敗,因為在內建類(lèi)型中,不存在這個(gè)屬性
實(shí)例屬性 vs 類(lèi)屬性
類(lèi)屬性?xún)H是與類(lèi)相關(guān)的數據值,和實(shí)例屬性不同,類(lèi)屬性和實(shí)例無(wú)關(guān)。這些值像靜態(tài)成員那樣被引用,即使在多次實(shí)例化中調用類(lèi),它們的值都保持不變。不管如何,靜態(tài)成員不會(huì )因為實(shí)例而改變它們的值,除非實(shí)例中顯式改變它們的值。類(lèi)和實(shí)例都是名字空間。類(lèi)是類(lèi)屬性的名字空間,實(shí)例則是實(shí)例屬性的。
關(guān)于類(lèi)屬性和實(shí)例屬性,還有一些方面需要指出??刹捎妙?lèi)來(lái)訪(fǎng)問(wèn)類(lèi)屬性,如果實(shí)例沒(méi)有同名的屬性的話(huà),你也可以用實(shí)例來(lái)訪(fǎng)問(wèn)。
訪(fǎng)問(wèn)類(lèi)屬性
類(lèi)屬性可通過(guò)類(lèi)或實(shí)例來(lái)訪(fǎng)問(wèn)。下面的示例中,類(lèi) C 在創(chuàng )建時(shí),帶一個(gè) version 屬性,這樣通過(guò)類(lèi)對象來(lái)訪(fǎng)問(wèn)它是很自然的了,比如,C.version
>>> c = C()
>>> C.version
2
>>> c.version
2
>>> C.version += 2
>>> C.version
4
>>> c.version
4
從實(shí)例中訪(fǎng)問(wèn)類(lèi)屬性須謹慎
與通常 Python 變量一樣,任何對實(shí)例屬性的賦值都會(huì )創(chuàng )建一個(gè)實(shí)例屬性(如果不存在的話(huà))并且對其賦值。如果類(lèi)屬性中存在同名的屬性,副作用即產(chǎn)生。
>>> foo =Foo()
>>> foo.x
1
>>> foo.x = 2
>>> Foo.x
1
使用del后
靜態(tài)成員,如其名所言,任憑整個(gè)實(shí)例(及其屬性)的如何進(jìn)展,它都不理不采(因此獨立于實(shí)例)。同時(shí),當一個(gè)實(shí)例在類(lèi)屬性被修改后才創(chuàng )建,那么更新的值就將生效。類(lèi)屬性的修改會(huì )影響到所有的實(shí)例:
正如上面所看到的那樣,使用實(shí)例屬性來(lái)試著(zhù)修改類(lèi)屬性是很危險的。原因在于實(shí)例擁有它們自已的屬性集,在 Python 中沒(méi)有明確的方法來(lái)指示你想要修改同名的類(lèi)屬性,修改類(lèi)屬性需要使用類(lèi)名,而不是實(shí)例名。
靜態(tài)方法和類(lèi)方法
靜態(tài)方法和類(lèi)方法在 Python2.2 中引入。經(jīng)典類(lèi)及新式(new-style)類(lèi)中都可以使用它。一對內建函數被引入,用于將作為類(lèi)定義的一部分的某一方法聲明“標記”(tag),“強制類(lèi)型轉換”(cast)或者“轉換”(convert)為這兩種類(lèi)型的方法之一。
現在讓我們看一下在經(jīng)典類(lèi)中創(chuàng )建靜態(tài)方法和類(lèi)方法的一些例子:
>>> class TestClassMethod:
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
foo = classmethod(foo)
對應的內建函數被轉換成它們相應的類(lèi)型,并且重新賦值給了相同的變量名。如果沒(méi)有調用這兩個(gè)函數,二者都會(huì )在 Python 編譯器中產(chǎn)生錯誤,顯示需要帶 self 的常規方法聲明。
使用函數修飾符:
在 Python2.4 中加入的新特征。你可以用它把一個(gè)函數應用到另個(gè)函數對象上, 而且新函數對象依然綁定在原來(lái)的變量。我們正是需要它來(lái)整理語(yǔ)法。通過(guò)使用 decorators,我們可以避免像上面那樣的重新賦值:
>>> class TestClassMethod:
@classmethod
def foo(cls):
print 'calling class method foo()'
print 'foo() is part of class:', cls.__name__
聯(lián)系客服