類和對象是面向對象程式設計的兩個主要方面。類建立一個新類型,而對象這個類的 執行個體 。這類似于你有一個<code>int</code>類型的變量,這存儲整數的變量是<code>int</code>類的執行個體(對象)。
類(class)::用來描述具有相同的屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的執行個體。
對象: 通過類定義的資料結構執行個體。對象包括兩個資料成員(類變量和執行個體變量)和方法。
執行個體化: 建立一個類的執行個體,類的具體對象。
方法: 類中定義的函數。
(方法和函數在python中是不同的概念,方法是類定義的函數,在類中的函數才叫方法。函數是就是一般的函數)
資料成員: 類變量或者執行個體變量用于處理類及其執行個體對象的相關的資料。
方法重載: 如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆寫(override),重載。
執行個體變量: 定義在方法中的變量,隻作用于目前執行個體的類。
類變量: 類變量在整個執行個體化的對象中是公用的。類變量定義在類中且在函數體之外。類變量通常不作為執行個體變量使用。
繼承
:即一個派生類(derived class)繼承基類(base class)的字段和方法。繼承也允許把一個派生類的對象作為一個基類對象對待。
給c/c++/java/c#程式員的注釋
注意,即便是整數也被作為對象(屬于<code>int</code>類)。這和c++、java(1.5版之前)把整數純粹作為類型是不同的。通過<code>help(int)</code>了解更多這個類的詳情。 c#和java 1.5程式員會熟悉這個概念,因為它類似與封裝與解封裝 的概念。
對象可以使用普通的 屬于 對象的變量存儲資料。屬于一個對象或類的變量被稱為域。對象也可以使用
屬于 類的函數來具有功能。這樣的函數被稱為類的方法。這些術語幫助我們把它們與孤立的函數和變量區分開來。域和方法可以合稱為類的屬性。
域有兩種類型——屬于每個執行個體/類的對象或屬于類本身。它們分别被稱為執行個體變量和類變量。
類使用<code>class</code>關鍵字建立。類的域和方法被列在一個縮進塊中。
在python類中定義的方法通常有三種:執行個體方法、類方法、靜态方法。
這三者之間的差別是:
執行個體方法:一般都以self作為第一個參數,必須和具體的對象執行個體進行綁定才能通路,
類方法 :以cls作為第一個參數,cls表示類本身,定義時使用@classmethod,那麼通過cls引用的必定是類對象的屬性和方法;
靜态方法:不需要預設的任何參數,跟一般的普通函數類似.定義的時候使用@staticmethod,靜态方法中不需要額外定義參數,是以在靜态方法中引用類屬性的話,必須通過類對象來引用。
而執行個體方法的第一個參數是執行個體對象self,那麼通過self引用的可能是類屬性、也有可能是執行個體屬性(這個需要具體分析)
不過在存在相同名稱的類屬性和執行個體屬性的情況下,執行個體屬性優先級更高。
可以直接在類外通過對象名通路,如果想定義成私有的,則需在前面加2個下劃線 ' __'
構造方法__init__()方法是一種特殊的方法,被稱為類的構造函數或初始化方法,當建立了這個類的執行個體時就會調用該方法.
構造方法支援重載,如果使用者自己沒有重新定義構造方法,系統就自動執行預設的構造方法。
析構方法__del__(self)在釋放對象時調用,支援重載,可以在裡面進行一些釋放資源的操作,不需要顯示調用。
建立執行個體對象
要建立一個類的執行個體,你可以使用類的名稱,并通過__init__方法接受參數。
"建立 employee 類的第一個對象"
emp1 = employee("zara", 2000)
"建立 employee 類的第二個對象"
emp2 = employee("manni", 5000)
通路屬性
使用點(.)來通路對象的屬性。使用如下類的名稱通路類變量:
emp1.displayemployee()
你可以添加,删除,修改類的屬性,如下所示:
emp1.age = 7 # 添加一個 'age' 屬性
emp1.age = 8 # 修改 'age' 屬性
del emp1.age # 删除 'age' 屬性
getattr(obj, name[, default]) : 通路對象的屬性。
hasattr(obj,name) : 檢查是否存在一個屬性。
setattr(obj,name,value) : 設定一個屬性。如果屬性不存在,會建立一個新屬性。
delattr(obj, name) : 删除屬性。
python内置類屬性
__dict__ : 類的屬性(包含一個字典,由類的資料屬性組成)
__doc__ :類的文檔字元串
__name__: 類名
__module__: 類定義所在的子產品(類的全名是'__main__.classname',
如果類位于一個導入子產品mymod中,那麼classname.__module__ 等于 mymod)
__bases__ : 類的所有父類構成元素(包含了以個由所有父類組成的元組)
python對象銷毀(垃圾回收)
在python内部記錄着所有使用中的對象各有多少引用。
一個内部跟蹤變量,稱為一個引用計數器。當對象被建立時, 就建立了一個引用計數, 當這個對象不再需要時,
也就是說, 這個對象的引用計數變為0 時,它被垃圾回收。但是回收不是"立即"的,
由解釋器在适當的時機,将垃圾對象占用的記憶體空間回收。
類的方法與普通的函數隻有一個特别的差別——它們必須有一個額外的第一個參數名稱,但是在調用這個方法的時候你不為這個參數指派,python會提供這個值。這個特别的變量指對象本身,按照慣例它的名稱是<code>self</code>。
雖然你可以給這個參數任何名稱,但是 強烈建議 你使用<code>self</code>這個名稱——其他名稱都是不贊成你使用的。使用一個标準的名稱有很多優點——你的程式讀者可以迅速識别它,如果使用<code>self</code>的話,還有些ide(內建開發環境)也可以幫助你。
給c++/java/c#程式員的注釋
python中的<code>self</code>等價于c++中的<code>self</code>指針和java、c#中的<code>this</code>參考。
你一定很奇怪python如何給<code>self</code>指派以及為何你不需要給它指派。舉一個例子會使此變得清晰。假如你有一個類稱為<code>myclass</code>和這個類的一個執行個體<code>myobject</code>。當你調用這個對象的方法<code>myobject.method(arg1, arg2)</code>的時候,這會由python自動轉為<code>myclass.method(myobject, arg1, arg2)</code>——這就是<code>self</code>的原理了。
這也意味着如果你有一個不需要參數的方法,你還是得給這個方法定義一個<code>self</code>參數。
我們使用<code>class</code>語句後跟類名,建立了一個新的類。這後面跟着一個縮進的語句塊形成類體。在這個例子中,我們使用了一個空白塊,它由<code>pass</code>語句表示。
可以注意到存儲對象的計算機記憶體位址也列印了出來。這個位址在你的計算機上會是另外一個值,因為python可以在任何空位存儲對象。
這裡我們看到了<code>self</code>的用法。注意<code>sayhi</code>方法沒有任何參數,但仍然在函數定義時有<code>self</code>。
在python的類中有很多方法的名字有特殊的重要意義。現在我們将學習<code>__init__</code>方法的意義。
這裡,我們把<code>__init__</code>方法定義為取一個參數<code>name</code>(以及普通的參數<code>self</code>)。在這個<code>__init__</code>裡,我們隻是建立一個新的域,也稱為<code>name</code>。注意它們是兩個不同的變量,盡管它們有相同的名字。點号使我們能夠區分它們。
最重要的是,我們沒有專門調用<code>__init__</code>方法,隻是在建立一個類的新執行個體的時候,把參數包括在圓括号内跟在類名後面,進而傳遞給<code>__init__</code>方法。
現在,我們能夠在我們的方法中使用<code>self.name</code>域。這在<code>sayhi</code>方法中得到了驗證。
<code>__init__</code>方法類似于c++、c#和java中的 constructor 。
已經讨論了類與對象的功能部分,現在來看一下它的資料部分。事實上,它們隻是與類和對象的名稱空間 綁定 的普通變量,即這些名稱隻在這些類與對象的前提下有效。
有兩種類型的 域 ——類的變量和對象的變量,它們根據是類還是對象 擁有 這個變量而區分。
類的變量 由一個類的所有對象(執行個體)共享使用。隻有一個類變量的拷貝,是以當某個對象對類的變量做了改動的時候,這個改動會反映到所有其他的執行個體上。
對象的變量 由類的每個對象/執行個體擁有。是以每個對象有自己對這個域的一份拷貝,即它們不是共享的,在同一個類的不同執行個體中,雖然對象的變量有相同的名稱,但是是互不相關的。通過一個例子會使這個易于了解。
這裡,<code>population</code>屬于<code>person</code>類,是以是一個類的變量。<code>name</code>變量屬于對象(它使用<code>self</code>指派)是以是對象的變量。
觀察可以發現<code>__init__</code>方法用一個名字來初始化<code>person</code>執行個體。在這個方法中,我們讓<code>population</code>增加<code>1</code>,這是因為我們增加了一個人。同樣可以發現,<code>self.name</code>的值根據每個對象指定,這表明了它作為對象的變量的本質。
記住,你隻能使用<code>self</code>變量來參考同一個對象的變量和方法。這被稱為 屬性參考 。
在這個程式中,我們還看到docstring對于類和方法同樣有用。我們可以在運作時使用<code>person.__doc__</code>和<code>person.sayhi.__doc__</code>來分别通路類與方法的文檔字元串。
就如同<code>__init__</code>方法一樣,還有一個特殊的方法<code>__del__</code>,它在對象消逝的時候被調用。對象消逝即對象不再被使用,它所占用的記憶體将傳回給系統作它用。在這個方法裡面,我們隻是簡單地把<code>person.population</code>減<code>1</code>。
當對象不再被使用時,<code>__del__</code>方法運作,但很難保證這個方法究竟在 什麼時候 運作。如果你想要指明它的運作,你就得使用<code>del</code>語句,就如同以前的例子中使用的那樣。
python中所有的類成員(包括資料成員)都是 公共的 ,所有的方法都是 有效的 。
隻有一個例外:如果你使用的資料成員名稱以 雙下劃線字首 比如<code>__privatevar</code>,python的名稱管理體系會有效地把它作為私有變量。
這樣就有一個慣例,如果某個變量隻想在類或對象中使用,就應該以單下劃線字首。而其他的名稱都将作為公共的,可以被其他類/對象使用。記住這隻是一個慣例,并不是python所要求的(與雙下劃線字首不同)。
同樣,注意<code>__del__</code>方法與 destructor 的概念類似。
面向對象的程式設計帶來的主要好處之一是代碼的重用,實作這種重用的方法之一是通過 繼承 機制。繼承完全可以了解成類之間的類型和子類型 關系。
假設你想要寫一個程式來記錄學校之中的教師和學生情況。他們有一些共同屬性,比如姓名、年齡和位址。他們也有專有的屬性,比如教師的薪水、課程和假期,學生的成績和學費。
你可以為教師和學生建立兩個獨立的類來處理它們,但是這樣做的話,如果要增加一個新的共有屬性,就意味着要在這兩個獨立的類中都增加這個屬性。這很快就會顯得不實用。
一個比較好的方法是建立一個共同的類稱為<code>schoolmember</code>然後讓教師和學生的類 繼承 這個共同的類。即它們都是這個類型(類)的子類型,然後我們再為這些子類型添加專有的屬性。
使用這種方法有很多優點。如果我們增加/改變了<code>schoolmember</code>中的任何功能,它會自動地反映到子類型之中。例如,你要為教師和學生都增加一個新的身份證域,那麼你隻需簡單地把它加到<code>schoolmember</code>類中。然而,在一個子類型之中做的改動不會影響到别的子類型。另外一個優點是你可以把教師和學生對象都作為<code>schoolmember</code>對象來使用,這在某些場合特别有用,比如統計學校成員的人數。一個子類型在任何需要父類型的場合可以被替換成父類型,即對象可以被視作是父類的執行個體,這種現象被稱為多态現象。
另外,我們會發現在 重用 父類的代碼的時候,我們無需在不同的類中重複它。而如果我們使用獨立的類的話,我們就不得不這麼做了。
在上述的場合中,<code>schoolmember</code>類被稱為 基本類 或 超類 。而<code>teacher</code>和<code>student</code>類被稱為導出類 或子類 。
現在,我們将學習一個例子程式。
為了使用繼承,我們把基本類的名稱作為一個元組跟在定義類時的類名稱之後。然後,我們注意到基本類的<code>__init__</code>方法專門使用<code>self</code>變量調用,這樣我們就可以初始化對象的基本類部分。這一點十分重要——python不會自動調用基本類的constructor,你得親自專門調用它。
我們還觀察到我們在方法調用之前加上類名稱字首,然後把<code>self</code>變量及其他參數傳遞給它。
注意,在我們使用<code>schoolmember</code>類的<code>tell</code>方法的時候,我們把<code>teacher</code>和<code>student</code>的執行個體僅僅作為<code>schoolmember</code>的執行個體。
另外,在這個例子中,我們調用了子類型的<code>tell</code>方法,而不是<code>schoolmember</code>類的<code>tell</code>方法。可以這樣來了解,python總是首先查找對應類型的方法,在這個例子中就是如此。如果它不能在導出類中找到對應的方法,它才開始到基本類中逐個查找。基本類是在類定義的時候,在元組之中指明的。
一個術語的注釋——如果在繼承元組中列了一個以上的類,那麼它就被稱作 多重繼承 。
繼承文法 :
class 派生類名(基類名)://... 基類名寫作括号裡,基本類是在類定義的時候,在元組之中指明的。
在python中繼承中的一些特點:
文法:
派生類的聲明,與他們的父類類似,繼承的基類清單跟在類名之後,如下所示:
class subclassname (parentclass1[, parentclass2, ...]):
'optional class documentation string'
class_suite
以上代碼執行結果如下:
你可以繼承多個類
class a: # 定義類 a
.....
class b: # 定義類 b
class c(a, b): # 繼承類 a 和 b
你可以使用issubclass()或者isinstance()方法來檢測。
issubclass() - 布爾函數判斷一個類是另一個類的子類或者子孫類,文法:issubclass(sub,sup)
isinstance(obj, class) 布爾函數如果obj是class類的執行個體對象或者是一個class子類的執行個體對象則傳回true。
方法重寫
如果你的父類方法的功能不能滿足你的需求,你可以在子類重寫你父類的方法:
執行個體:
執行以上代碼輸出結果如下:
下表列出了一些通用的功能,你可以在自己的類重寫:
序号 方法, 描述 & 簡單的調用
1 __init__ ( self [,args...] ) 構造函數 簡單的調用方法: obj = classname(args)
2 __del__( self ) 析構方法, 删除一個對象 簡單的調用方法 : dell obj
3 __repr__( self ) 轉化為供解釋器讀取的形式 簡單的調用方法 : repr(obj)
4 __str__( self ) 用于将值轉化為适于人閱讀的形式 簡單的調用方法 : str(obj)
5 __cmp__ ( self, x ) 對象比較 簡單的調用方法 : cmp(obj, x) 運算符重載
python同樣支援運算符重載,執行個體如下:
類屬性與方法
類的私有屬性
__private_attrs:兩個下劃線開頭,聲明該屬性為私有,不能在類地外部被使用或直接通路。
在類内部的方法中使用時 self.__private_attrs。
類的方法
在類地内部,使用def關鍵字可以為類定義一個方法,與一般函數定義不同,類方法必須包含參數self,且為第一個參數
類的私有方法
__private_method: 兩個下劃線開頭,聲明該方法為私有方法,不能在類地外部調用。在類的内部調用 slef.__private_methods
python 通過改變名稱來包含類名:
1 2 2
traceback (most recent call last):
file "test.py", line 17, in <module>
print counter.__secretcount # 報錯,執行個體不能通路私有變量
attributeerror: justcounter instance has no attribute '__secretcount'
python不允許執行個體化的類通路私有資料,但你可以使用 object._classname__attrname 通路屬性,将如下代碼替
換以上代碼的最後一行代碼:
.........................
print counter._justcounter__secretcount
執行以上代碼,執行結果如下:
1 2 2 2
在進行python面向對象程式設計之前,先來了解幾個術語:類,類對象,執行個體對象,屬性,函數和方法。
類是對現實世界中一些事物的封裝,定義一個類可以采用下面的方式來定義:
class classname:
block
注意類名後面有個冒号,在block塊裡面就可以定義屬性和方法了。當一個類定義完之後,就産生了一個類對象。類對象支援兩種操作:引用和執行個體化。引用操作是通過類對象去調用類中的屬性或者方法,而執行個體化是産生出一個類對象的執行個體,稱作執行個體對象。比如定義了一個people類:
class people:
name = 'jack' #定義了一個屬性
#定義了一個方法
def printname(self):
print self.name
people類定義完成之後就産生了一個全局的類對象,可以通過類對象來通路類中的屬性和方法了。當通過people.name(至于為什麼可以直接這樣通路屬性後面再解釋,這裡隻要了解類對象這個概念就行了)來通路時,people.name中的people稱為類對象,這點和c++中的有所不同。當然還可以進行執行個體化操作,p=people( ),這樣就産生了一個people的執行個體對象,此時也可以通過執行個體對象p來通路屬性或者方法了(p.name).
了解了類、類對象和執行個體對象的差別之後,我們來了解一下python中屬性、方法和函數的差別。
在上面代碼中注釋的很清楚了,name是一個屬性,printname( )是一個方法,與某個對象進行綁定的函數稱作為方法。一般在類裡面定義的函數與類對象或者執行個體對象綁定了,是以稱作為方法;而在類外定義的函數一般沒有同對象進行綁定,就稱為函數。
在類中我們可以定義一些屬性,比如:
name = 'jack'
age = 12
p = people()
print p.name,p.age
定義了一個people類,裡面定義了name和age屬性,預設值分别為'jack'和12。在定義了類之後,就可以用來産生執行個體化對象了,這句p = people( )執行個體化了一個對象p,然後就可以通過p來讀取屬性了。這裡的name和age都是公有的,可以直接在類外通過對象名通路,如果想定義成私有的,則需在前面加2個下劃線 ' __'。
__name = 'jack'
__age = 12
print p.__name,p.__age
這段程式運作會報錯:
traceback (most recent call last):
file "c:/pycharmprojects/firstproject/oop.py", line 6, in <module>
print p.__name,p.__age
attributeerror: people instance has no attribute '__name
提示找不到該屬性,因為私有屬性是不能夠在類外通過對象名來進行通路的。在python中沒有像c++中public和private這些關鍵字來差別公有屬性和私有屬性,它是以屬性命名方式來區分,如果在屬性名前面加了2個下劃線'__',則表明該屬性是私有屬性,否則為公有屬性(方法也是一樣,方法名前面加了2個下劃線的話表示該方法是私有的,否則為公有的)。
在類中可以根據需要定義一些方法,定義方法采用def關鍵字,在類中定義的方法至少會有一個參數,,一般以名為'self'的變量作為該參數(用其他名稱也可以),而且需要作為第一個參數。下面看個例子:
def getname(self):
return self.__name
def getage(self):
return self.__age
print p.getname(),p.getage()
如果對self不好了解的話,可以把它當做c++中類裡面的this指針一樣了解,就是對象自身的意思,在用某個對象調用該方法時,就将該對象作為第一個參數傳遞給self。
在python中有一些内置的方法,這些方法命名都有比較特殊的地方(其方法名以2個下劃線開始然後以2個下劃線結束)。類中最常用的就是構造方法和析構方法。
構造方法__init__(self,....):在生成對象時調用,可以用來進行一些初始化操作,不需要顯示去調用,系統會預設去執行。構造方法支援重載,如果使用者自己沒有重新定義構造方法,系統就自動執行預設的構造方法。
析構方法__del__(self):在釋放對象時調用,支援重載,可以在裡面進行一些釋放資源的操作,不需要顯示調用。
還有其他的一些内置方法,比如 __cmp__( ), __len( )__等。下面是常用的内置方法:
内置方法
說明
__init__(self,...)
初始化對象,在建立新對象時調用
__del__(self)
釋放對象,在對象被删除之前調用
__new__(cls,*args,**kwd)
執行個體的生成操作
__str__(self)
在使用print語句時被調用
__getitem__(self,key)
擷取序列的索引key對應的值,等價于seq[key]
__len__(self)
在調用内聯函數len()時被調用
__cmp__(stc,dst)
比較兩個對象src和dst
__getattr__(s,name)
擷取屬性的值
__setattr__(s,name,value)
設定屬性的值
__delattr__(s,name)
删除name屬性
__getattribute__()
__getattribute__()功能與__getattr__()類似
__gt__(self,other)
判斷self對象是否大于other對象
__lt__(slef,other)
判斷self對象是否小于other對象
__ge__(slef,other)
判斷self對象是否大于或者等于other對象
__le__(slef,other)
判斷self對象是否小于或者等于other對象
__eq__(slef,other)
判斷self對象是否等于other對象
__call__(self,*args)
把執行個體對象作為函數調用
__init__():__init__方法在類的一個對象被建立時,馬上運作。這個方法可以用來對你的對象做一些你希望的初始化。注意,這個名稱的開始和結尾都是雙下劃線。代碼例子:
# filename: class_init.py
class person:
def __init__(self, name):
self.name = name
def sayhi(self):
print 'hello, my name is', self.name
p = person('swaroop')
p.sayhi()
輸出:
hello, my name is swaroop
__new__():__new__()在__init__()之前被調用,用于生成執行個體對象。利用這個方法和類屬性的特性可以實作設計模式中的單例模式。單例模式是指建立唯一對象嗎,單例模式設計的類隻能執行個體化一個對象。
# -*- coding: utf-8 -*-
class singleton(object):
__instance = none # 定義執行個體
def __init__(self):
pass
def __new__(cls, *args, **kwd): # 在__init__之前調用
if singleton.__instance is none: # 生成唯一執行個體
singleton.__instance = object.__new__(cls, *args, **kwd)
return singleton.__instance
__getattr__()、__setattr__()和__getattribute__():當讀取對象的某個屬性時,python會自動調用__getattr__()方法。例如,fruit.color将轉換為fruit.__getattr__(color)。當使用指派語句對屬性進行設定時,python會自動調用__setattr__()方法。__getattribute__()的功能與__getattr__()類似,用于擷取屬性的值。但是__getattribute__()能提供更好的控制,代碼更健壯。注意,python中并不存在__setattribute__()方法。代碼例子:
class fruit(object):
def __init__(self, color="red", price=0):
self.__color = color
self.__price = price
def __getattribute__(self, item): # <span style="font-family:宋體;font-size:12px;">擷取屬性的方法</span>
return object.__getattribute__(self, item)
def __setattr__(self, key, value):
self.__dict__[key] = value
if __name__ == "__main__":
fruit = fruit("blue", 10)
print fruit.__dict__.get("_fruit__color") # <span style="font-family:宋體;font-size:12px;">擷取color屬性</span>
fruit.__dict__["_fruit__price"] = 5
print fruit.__dict__.get("_fruit__price") # <span style="font-family:宋體;font-size:12px;">擷取price屬性</span>
python不允許執行個體化的類通路私有資料,但你可以使用object._classname__attrname通路這些私有屬性。
__getitem__():如果類把某個屬性定義為序列,可以使用__getitem__()輸出序列屬性中的某個元素.假設水果店中銷售多鐘水果,可以通過__getitem__()方法擷取水果店中的沒種水果。代碼例子:
class fruitshop:
def __getitem__(self, i): # 擷取水果店的水果
return self.fruits[i]
shop = fruitshop()
shop.fruits = ["apple", "banana"]
print shop[1]
for item in shop: # 輸出水果店的水果
print item,
輸出:
banana
apple banana
__str__():__str__()用于表示對象代表的含義,傳回一個字元串.實作了__str__()方法後,可以直接使用print語句輸出對象,也可以通過函數str()觸發__str__()的執行。這樣就把對象和字元串關聯起來,便于某些程式的實作,可以用這個字元串來表示某個類。代碼例子:
class fruit:
'''''fruit類''' #為fruit類定義了文檔字元串
def __str__(self): # 定義對象的字元串表示
return self.__doc__
fruit = fruit()
print str(fruit) # 調用内置函數str()觸發__str__()方法,輸出結果為:fruit類
print fruit #直接輸出對象fruit,傳回__str__()方法的值,輸出結果為:fruit類
__call__():在類中實作__call__()方法,可以在對象建立時直接傳回__call__()的内容。使用該方法可以模拟靜态方法。代碼例子:
class fruit:
class growth: # 内部類
def __call__(self):
print "grow ..."
grow = growth() # 調用growth(),此時将類growth作為函數傳回,即為外部類fruit定義方法grow(),grow()将執行__call__()内的代碼
if __name__ == '__main__':
fruit.grow() # 輸出結果:grow ...
在了解了類基本的東西之後,下面看一下python中這幾個概念的差別。
先來談一下類屬性和執行個體屬性
在前面的例子中我們接觸到的就是類屬性,顧名思義,類屬性就是類對象所擁有的屬性,它被所有類對象的執行個體對象所共有,在記憶體中隻存在一個副本,這個和c++中類的靜态成員變量有點類似。對于公有的類屬性,在類外可以通過類對象和執行個體對象通路。
name = 'jack' #公有的類屬性
__age = 12 #私有的類屬性
print p.name #正确
print people.name #正确
print p.__age #錯誤,不能在類外通過執行個體對象通路私有的類屬性
print people.__age #錯誤,不能在類外通過類對象通路私有的類屬性
執行個體屬性是不需要在類中顯示定義的,比如:
p.age =12
print p.name #正确
print p.age #正确
print people.name #正确
print people.age #錯誤
在類外對類對象people進行執行個體化之後,産生了一個執行個體對象p,然後p.age = 12這句給p添加了一個執行個體屬性age,指派為12。這個執行個體屬性是執行個體對象p所特有的,注意,類對象people并不擁有它(是以不能通過類對象來通路這個age屬性)。當然還可以在執行個體化對象的時候給age指派。
#__init__()是内置的構造方法,在執行個體化對象時自動調用
def __init__(self,age):
self.age = age
p = people(12)
如果需要在類外修改類屬性,必須通過類對象去引用然後進行修改。如果通過執行個體對象去引用,會産生一個同名的執行個體屬性,這種方式修改的是執行個體屬性,不會影響到類屬性,并且之後如果通過執行個體對象去引用該名稱的屬性,執行個體屬性會強制屏蔽掉類屬性,即引用的是執行個體屬性,除非删除了該執行個體屬性。
country = 'china'
print people.country
print p.country
p.country = 'japan'
print p.country #執行個體屬性會屏蔽掉同名的類屬性
del p.country #删除執行個體屬性
下面來看一下類方法、執行個體方法和靜态方法的差別。
類方法:是類對象所擁有的方法,需要用修飾器"@classmethod"來辨別其為類方法,對于類方法,第一個參數必須是類對象,一般以"cls"作為第一個參數(當然可以用其他名稱的變量作為其第一個參數,但是大部分人都習慣以'cls'作為第一個參數的名字,就最好用'cls'了),能夠通過執行個體對象和類對象去通路。
#類方法,用classmethod來進行修飾
@classmethod
def getcountry(cls):
return cls.country
print p.getcountry() #可以用過執行個體對象引用
print people.getcountry() #可以通過類對象引用
類方法還有一個用途就是可以對類屬性進行修改:
def setcountry(cls,country):
cls.country = country
p.setcountry('japan')
print p.getcountry()
print people.getcountry()
運作結果:
china
japan
結果顯示在用類方法對類屬性修改之後,通過類對象和執行個體對象通路都發生了改變。
執行個體方法:在類中最常定義的成員方法,它至少有一個參數并且必須以執行個體對象作為其第一個參數,一般以名為'self'的變量作為第一個參數(當然可以以其他名稱的變量作為第一個參數)。在類外執行個體方法隻能通過執行個體對象去調用,不能通過其他方式去調用。
#執行個體方法
def getcountry(self):
return self.country
print p.getcountry() #正确,可以用過執行個體對象引用
print people.getcountry() #錯誤,不能通過類對象引用執行個體方法
靜态方法:需要通過修飾器"@staticmethod"來進行修飾,靜态方法不需要多定義參數。
@staticmethod
#靜态方法
def getcountry():
return people.country
對于類屬性和執行個體屬性,如果在類方法中引用某個屬性,該屬性必定是類屬性,而如果在執行個體方法中引用某個屬性(不作更改),并且存在同名的類屬性,此時若執行個體對象有該名稱的執行個體屬性,則執行個體屬性會屏蔽類屬性,即引用的是執行個體屬性,若執行個體對象沒有該名稱的執行個體屬性,則引用的是類屬性;如果在執行個體方法更改某個屬性,并且存在同名的類屬性,此時若執行個體對象有該名稱的執行個體屬性,則修改的是執行個體屬性,若執行個體對象沒有該名稱的執行個體屬性,則會建立一個同名稱的執行個體屬性。想要修改類屬性,如果在類外,可以通過類對象修改,如果在類裡面,隻有在類方法中進行修改。
從類方法和執行個體方法以及靜态方法的定義形式就可以看出來,類方法的第一個參數是類對象cls,那麼通過cls引用的必定是類對象的屬性和方法;而執行個體方法的第一個參數是執行個體對象self,那麼通過self引用的可能是類屬性、也有可能是執行個體屬性(這個需要具體分析),不過在存在相同名稱的類屬性和執行個體屬性的情況下,執行個體屬性優先級更高。靜态方法中不需要額外定義參數,是以在靜态方法中引用類屬性的話,必須通過類對象來引用。
上面談到了類的基本定義和使用方法,這隻展現了面向對象程式設計的三大特點之一:封裝。下面就來了解一下另外兩大特征:繼承和多态。
在python中,如果需要的話,可以讓一個類去繼承一個類,被繼承的類稱為父類或者超類、也可以稱作基類,繼承的類稱為子類。并且python支援多繼承,能夠讓一個子類有多個父類。
python中類的繼承定義基本形式如下:
#父類
class superclassname:
#子類
class subclassname(superclassname):
在定義一個類的時候,可以在類名後面緊跟一對括号,在括号中指定所繼承的父類,如果有多個父類,多個父類名之間用逗号隔開。以大學裡的學生和老師舉例,可以定義一個父類universitymember,然後類student和類teacher分别繼承類universitymember:
class universitymember:
def __init__(self,name,age):
return self.name
return self.age
class student(universitymember):
def __init__(self,name,age,sno,mark):
universitymember.__init__(self,name,age) #注意要顯示調用父類構造方法,并傳遞參數self
self.sno = sno
self.mark = mark
def getsno(self):
return self.sno
def getmark(self):
return self.mark
class teacher(universitymember):
def __init__(self,name,age,tno,salary):
universitymember.__init__(self,name,age)
self.tno = tno
self.salary = salary
def gettno(self):
return self.tno
def getsalary(self):
return self.salary
在大學中的每個成員都有姓名和年齡,而學生有學号和分數這2個屬性,老師有教工号和工資這2個屬性,從上面的代碼中可以看到:
1)在python中,如果父類和子類都重新定義了構造方法__init( )__,在進行子類執行個體化的時候,子類的構造方法不會自動調用父類的構造方法,必須在子類中顯示調用。
2)如果需要在子類中調用父類的方法,需要以”父類名.方法“這種方式調用,以這種方式調用的時候,注意要傳遞self參數過去。
對于繼承關系,子類繼承了父類所有的公有屬性和方法,可以在子類中通過父類名來調用,而對于私有的屬性和方法,子類是不進行繼承的,是以在子類中是無法通過父類名來通路的。
python支援多重繼承。對于多重繼承,比如
class subclass(superclass1,superclass2)
此時有一個問題就是如果subclass沒有重新定義構造方法,它會自動調用哪個父類的構造方法?這裡記住一點:以第一個父類為中心。如果subclass重新定義了構造方法,需要顯示去調用父類的構造方法,此時調用哪個父類的構造方法由你自己決定;若subclass沒有重新定義構造方法,則隻會執行第一個父類的構造方法。并且若superclass1和superclass2中有同名的方法,通過子類的執行個體化對象去調用該方法時調用的是第一個父類中的方法。
本質上,多态意味着可以對不同的對象使用同樣的操作,但它們可能會以多種形态呈現出結果。len(object)函數就展現了這一點。在c++、java、c#這種編譯型語言中,由于有編譯過程,是以就鮮明地分成了運作時多态和編譯時多态。運作時多态是指允許父類指針或名稱來引用子類對象,或對象方法,而實際調用的方法為對象的類類型方法,這就是所謂的動态綁定。編譯時多态有模闆或範型、方法重載(overload)、方法重寫(override)等。而python是動态語言,動态地确定類型資訊恰恰展現了多态的特征。在python中,任何不知道對象到底是什麼類型,但又需要對象做點什麼的時候,都會用到多态。
能夠直接說明多态的兩段示例代碼如下:
1、方法多态
_metaclass_=type # 确定使用新式類
class calculator:
def count(self,args):
return 1
calc=calculator() #自定義類型
from random import choice
obj=choice(['hello,world',[1,2,3],calc]) #obj是随機傳回的 類型不确定
print type(obj)
print obj.count('a') #方法多态
對于一個臨時對象obj,它通過python的随機函數取出來,不知道具體類型(是字元串、元組還是自定義類型),都可以調用count方法進行計算,至于count由誰(哪種類型)去做怎麼去實作我們并不關心。
class duck:
def quack(self):
print "quaaaaaack!"
def feathers(self):
print "the duck has white and gray feathers."
def quack(self):
print "the person imitates a duck."
print "the person takes a feather from the ground and shows it."
def in_the_forest(duck):
duck.quack()
duck.feathers()
def game():
donald = duck()
john = person()
in_the_forest(donald)
in_the_forest(john)
game()
就in_the_forest函數而言,參數對象是一個鴨子類型,它實作了方法多态。但是實際上我們知道,從嚴格的抽象來講,person類型和duck完全風馬牛不相及。
2、運算符多态
def add(x,y):
return x+y
print add(1,2) #輸出3
print add("hello,","world") #輸出hello,world
print add(1,"abc") #抛出異常 typeerror: unsupported operand type(s) for +: 'int' and 'str'
上例中,顯而易見,python的加法運算符是”多态“的,理論上,我們實作的add方法支援任意支援加法的對象,但是我們不用關心兩個參數x和y具體是什麼類型。
python同樣支援運算符重載,執行個體如下:
class vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
return vector(self.a + other.a, self.b + other.b)
v1 = vector(2,10)
v2 = vector(5,-2)
print v1 + v2
一兩個示例代碼當然不能從根本上說明多态。普遍認為面向對象最有價值最被低估的特征其實是多态。我們所了解的多态的實作和子類的虛函數位址綁定有關系,多态的效果其實和函數位址運作時動态綁定有關。在c++,
java, c#中實作多态的方式通常有重寫和重載兩種,從上面兩段代碼,我們其實可以分析得出python中實作多态也可以變相了解為重寫和重載。在python中很多内置函數和運算符都是多态的。
參考文獻:
<a target="_blank" href="http://www.cnblogs.com/dolphin0520/archive/2013/03/29/2986924.html">http://www.cnblogs.com/dolphin0520/archive/2013/03/29/2986924.html</a>
<a target="_blank" href="http://www.cnblogs.com/jeffwongishandsome/archive/2012/10/06/2713258.html">http://www.cnblogs.com/jeffwongishandsome/archive/2012/10/06/2713258.html</a>