天天看點

Python基礎(11)--面向對象1

面向對象設計與面向對象程式設計的關系

面向對象設計(ood)不會特别要求面向對象程式設計語言。事實上,ood 可以由純結構化語言來實作,比如 c,但如果想要構造具備對象性質和特點的資料類型,就需要在程式上作更多的努力。當一門語言内建 oo 特性,oo 程式設計開發就會更加友善高效。另一方面,一門面向對象的語言不一定會強制你寫 oo 方面的程式。例如 c++可以被認為“更好的c”;而 java,則要求萬物皆類,此外還規定,一個源檔案對應一個類定義。然而,在 python 中,類和 oop 都不是日常程式設計所必需的。盡管它從一開始設計就是面向對象的,并且結構上支援 oop,但python 沒有限定或要求你在你的應用中寫 oo 的代碼。oop 是一門強大的工具,不管你是準備進入,學習,過渡,或是轉向 oop,都可以任意支配。考慮用 ood 來工作的一個最重要的原因,在于它直接提供模組化和解決現實世界問題和情形的途徑。

類是一種資料結構,我們可以用它來定義對象,後者把資料值和行為特性融合在一起。類是現實世界的抽象的實體以程式設計形式出現。執行個體是這些對象的具體

化。可以類比一下,類是藍圖或者模型,用來産生真實的物體(執行個體)。類還可以派生出相似但有差異的子類。程式設計中類的概念就應用了很多這樣的特征。在

python 中,類聲明與函數聲明很相似,頭一行用一個相應的關鍵字,接下來是一個作為它的定義的代碼體,如下所示:

二者都允許你在他們的聲明中建立函數,閉包或者内部函數(即函數内的函數),還有在類中定義的方法。最大的不同在于你運作函數,而類會建立一個對象。類就像一個 python 容器類型。盡管類是對象(在 python 中,一切皆對象),但正被定義時,它們還不是對象的實作。

建立類 

python 類使用 class 關鍵字來建立。簡單的類的聲明可以是關鍵字後緊跟類名:

基類是一個或多個用于繼承的父類的集合;類體由所有聲明語句,類成員定義,資料屬性和函數組成。類通常在一個子產品的頂層進行定義,以便類執行個體能夠在類所定義的源代碼檔案中的任何地方被建立。

聲明與定義

對于 python

函數來說,聲明與定義類沒什麼差別,因為他們是同時進行的,定義(類體)緊跟在聲明(含 class 關鍵字的頭行[header

line])和可選的文檔字元串後面。同時,所有的方法也必須同時被定義。如果對 oop 很熟悉,請注意 python 并不支援純虛函數(像

c++)或者抽象方法(如在 java 中),這些都強制程式員在子類中定義方法。作為替代方法,你可以簡單地在基類方法中引發

notimplementederror 異常,這樣可以獲得類似的效果。

屬性就是屬于另一個對象的資料或者函數元素,可以通過我們熟悉的句點屬性辨別法來通路。一些 python 類型比如複數有資料屬性(實部和虛部),而另外一些,像清單和字典,擁有方法(函數屬性)。

有關屬性的一個有趣的地方是,當你正通路一個屬性時,它同時也是一個對象,擁有它自己的屬性,可以通路,這導緻了一個屬性鍊,比如,mything,subthing,subsubthing.等等

資料屬性僅僅是所定義的類的變量。它們可以像任何其它變量一樣在類建立後被使用,并且,要麼是由類中的方法來更新,要麼是在主程式其它什麼地方被更新。

種屬性已為 oo 程式員所熟悉,即靜态變量,或者是靜态資料。它們表示這些資料是與它們所屬的類對象綁定的,不依賴于任何類執行個體。如果你是一位

java 或 c++程式員,這種類型的資料相當于在一個變量聲明前加上 static 關鍵字。靜态成員通常僅用來跟蹤與類相關的值。

看下面的例子,使用類資料屬性(foo):

任何像函數一樣對 mynoactionmethod 自身的調用都将失敗:

>>> mynoactionmethod() traceback (innermost last):

file "<stdin>", line 1, in ?

mynoactionmethod() nameerror: mynoactionmethod

甚至由類對象調用此方法也失敗了。

>>> myclass.mynoactionmethod() traceback (innermost last):

myclass.mynoactionmethod()

typeerror: unbound method must be called with class

instance 1st argument

綁定(綁定及非綁定方法)

為與 oop 慣例保持一緻,python 嚴格要求,沒有執行個體,方法是不能被調用的。這種限制即 python所描述的綁定概念(binding),在此,方法必須綁定(到一個執行個體)才能直接被調用。非綁定的方法可能可以被調用,但執行個體對象一定要明确給出,才能確定調用成功。然而,不管是否綁定,方法都是它所在的類的固有屬性,即使它們幾乎總是通過執行個體來調用的。

決定類的屬性 

要知道一個類有哪些屬性,有兩種方法。最簡單的是使用 dir()内建函數。另外是通過通路類的字典屬性__dict__,這是所有類都具備的特殊屬性之一。

看一下下面的例子:

運作結果:

['__class__', '__delattr__',

'__dict__', '__doc__', '__format__', '__getattribute__', '__hash__',

'__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__',

'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',

'__weakref__', 'myversion', 'showvesion']

使用:

dict_proxy({'__module__':

'__main__', 'showvesion': <function showvesion at 0x0134c9b0>,

'__dict__': <attribute '__dict__' of 'myclass' objects>,

'myversion': '1.1', '__weakref__': <attribute '__weakref__' of

'myclass' objects>, '__doc__': 'myclass class definition'})

從上面可以看到,dir()傳回的僅是對象的屬性的一個名字清單,而__dict__傳回的是一個字典,它的鍵(keys)是屬性名,鍵值(values)是相應的屬性對象的資料值。

果還顯示了 myclass 類中兩個熟悉的屬性,showmyversion 和

myversion,以及一些新的屬性。這些屬性,__doc__及__module__,是所有類都具備的特殊類屬性(另外還有__dict__)。。

内建的 vars()函數接受類對象作為參數,傳回類的__dict__屬性的内容。

對任何類C,表顯示了類C的所有特殊屬性:  

c.__name__        類C的名字(字元串)

c.__doc__         類C的文檔字元串

c.__bases__       類C的所有父類構成的元組

c.__dict__        類C的屬性

c.__module__      類C定義所在的子產品(1.5 版本新增)

c.__class__       執行個體C對應的類(僅新式類中)

如果說類是一種資料結構定義類型,那麼執行個體則聲明了一個這種類型的變量。執行個體是那些主要用在運作期時的對象,類被執行個體化得到執行個體,該執行個體的類型就是這個被執行個體化的類。

python 的方式更加簡單。一旦定義了一個類,建立執行個體比調用一個函數還容易------不費吹灰之力。執行個體化的實作,可以使用函數操作符,如下示:

當類被調用,執行個體化的第一步是建立執行個體對象。一旦對象建立了,python

檢查是否實作了__init__()方法。預設情況下,如果沒有定義(或覆寫)特殊方法__init__(),對執行個體不會施加任何特别的操作.任何所需的

特定操作,都需要程式員實作__init__(),覆寫它的預設行為。

如果__init__()沒有實作,則傳回它的對象,執行個體化過程完畢。

如果__init__()已經被實作,那麼它将被調用,執行個體對象作為第一個參數(self)被傳遞進去,像标準方法調用一樣。調用類時,傳進的任何參數都交給了__init__()。實際中,你可以想像成這樣:把建立執行個體的調用當成是對構造器的調用。

與__init__()相比,__new__()方法更像一個真正的構造器。需要一種途徑來執行個體化不可變對象,比如,派生字元串,數字,等等。在這

種情況下,解釋器則調用類的__new__()方法,一個靜态方法,并且傳入的參數是在類執行個體化操作時生成的。__new__()會調用父類的

__new__()來建立對象(向上代理)。__new__()必須傳回一個合法的執行個體。

同樣,有一個相應的特殊解構器(destructor)方法名為__del__()。然而,由于 python

具有垃圾對象回收機制(靠引用計數),這個函數要直到該執行個體對象所有的引用都被清除掉後才會執行。python

中的解構器是在執行個體釋放前提供特殊處理功能的方法,它們通常沒有被實作,因為執行個體很少被顯式釋放。

注意:python 沒有提供任何内部機制來跟蹤一個類有多少個執行個體被建立了,或者記錄這些執行個體是些什麼東西。如果需要這些功能,你可以顯式加入一些代碼到類定義或者__init__()和__del__()中去。最好的方式是使用一個靜态成員來記錄執行個體的個數。靠儲存它們的引用來跟蹤執行個體對象是很危險的,因為你必須合理管理這些引用,不然,你的引用可能沒辦法釋放(因為還有其它的引用)!看下面一個例子:

設定執行個體的屬性可以在執行個體建立後任意時間進行,也可以在能夠通路執行個體的代碼中進行。構造器__init()__是設定這些屬性的關鍵點之一

能夠在“運作時”建立執行個體屬性,是 python 類的優秀特性之一,python 不僅是動态類型,而且在運作時,允許這些對象屬性的動态建立。這種特性讓人愛不釋

手。當然,建立這樣的屬性時,必須謹慎。一個缺陷是,屬性在條件語句中建立,如果該條件語句塊并未被執行,屬性也就不存在,而你在後面的代碼中試着去通路這些屬性,就會有錯誤發生。

預設參數提供預設的執行個體安裝

在實際應用中,帶預設參數的__init__()提供一個有效的方式來初始化執行個體。在很多情況下,預設值表示設定執行個體屬性的最常見的情況,如果提供了預設值,我們就沒必要顯式給構造器傳值了。

函數所有的靈活性,比如預設參數,也可以應用到方法中去。在執行個體化時,可變長度參數也是一個好的特性

__init__()應當傳回 none

采用函數操作符調用類對象會建立一個類執行個體,也就是說這樣一種調用過程傳回的對象就是執行個體,下面示例可以看出:

如果定義了構造器,它不應當傳回任何對象,因為執行個體對象是自動在執行個體化調用後傳回的。相應地,__init__()就不應當傳回任何對象(應當為 none);否則,就可能出現沖突,因為隻能傳回執行個體。試着傳回非 none 的任何其它對象都會導緻 typeerror 異常:

内建函數 dir()可以顯示類屬性,同樣還可以列印所有執行個體屬性:

與類相似,執行個體也有一個__dict__特殊屬性(可以調用 vars()并傳入一個執行個體來擷取),它是執行個體屬性構成的一個字典:

執行個體僅有兩個特殊屬性。對于任意對象i:

i.__class__      執行個體化 i 的類

i.__dict__       i 的屬性

内建類型也是類,對内建類型也可以使用dir(),與任何其它對象一樣,可以得到一個包含它屬性名字的清單:

Python基礎(11)--面向對象1

試着通路__dict__會失敗,因為在内建類型中,不存在這個屬性

類屬性僅是與類相關的資料值,和執行個體屬性不同,類屬性和執行個體無關。這些值像靜态成員那樣被引用,即使在多次執行個體化中調用類,它們的值都保持不變。不管如何,靜态成員不會因為執行個體而改變它們的值,除非執行個體中顯式改變它們的值。類和執行個體都是名字空間。類是類屬性的名字空間,執行個體則是執行個體屬性的。

關于類屬性和執行個體屬性,還有一些方面需要指出。可采用類來通路類屬性,如果執行個體沒有同名的屬性的話,你也可以用執行個體來通路。

通路類屬性

類屬性可通過類或執行個體來通路。下面的示例中,類 c 在建立時,帶一個 version 屬性,這樣通過類對象來通路它是很自然的了,比如,c.version

從執行個體中通路類屬性須謹慎

與通常 python 變量一樣,任何對執行個體屬性的指派都會建立一個執行個體屬性(如果不存在的話)并且對其指派。如果類屬性中存在同名的屬性,副作用即産生。

使用del後

靜态成員,如其名所言,任憑整個執行個體(及其屬性)的如何進展,它都不理不采(是以獨立于執行個體)。同時,當一個執行個體在類屬性被修改後才建立,那麼更新的值就将生效。類屬性的修改會影響到所有的執行個體:

正如上面所看到的那樣,使用執行個體屬性來試着修改類屬性是很危險的。原因在于執行個體擁有它們自已的屬性集,在 python 中沒有明确的方法來訓示你想要修改同名的類屬性,修改類屬性需要使用類名,而不是執行個體名。

靜态方法和類方法在 python2.2

中引入。經典類及新式(new-style)類中都可以使用它。一對内建函數被引入,用于将作為類定義的一部分的某一方法聲明“标記”(tag),“強制

類型轉換”(cast)或者“轉換”(convert)為這兩種類型的方法之一。

現在讓我們看一下在經典類中建立靜态方法和類方法的一些例子:

對應的内建函數被轉換成它們相應的類型,并且重新指派給了相同的變量名。如果沒有調用這兩個函數,二者都會在 python 編譯器中産生錯誤,顯示需要帶 self 的正常方法聲明。

使用函數修飾符:

在 python2.4 中加入的新特征。你可以用它把一個函數應用到另個函數對象上, 而且新函數對象依然綁定在原來的變量。我們正是需要它來整理文法。通過使用 decorators,我們可以避免像上面那樣的重新指派: