引言
提到面向對象,總是離不開幾個重要的術語:多態(Polymorphism),繼承(Inheritance)和封裝(Encapsulation)。Python也是一種支持OOP的動態語言,本文將簡單闡述python對面向對象的支持。
在討論Python的OOP之前,先看幾個OOP術語的定義:
類:對具有相同數據和方法的一組對象的描述或定義。
對象:對象是一個類的實例。
實例(instance):一個對象的實例化實現。
標識(identity):每個對象的實例都需要一個可以唯一標識這個實例的標記。
實例屬性(instance attribute):一個對象就是一組屬性的集合。
實例方法(instance method):所有存取或者更新對象某個實例一條或者多條屬性的函數的集合。
類屬性(classattribute):屬於一個類中所有對象的屬性,不會只在某個實例上發生變化
類方法(classmethod):那些無須特定的對性實例就能夠工作的從屬於類的函數。
1.Python中的類與對象
Python中定義類的方式比較簡單:
class 類名:
類變量
def __init__(self,paramers):
def 函數(self,...)
…...
其中直接定義在類體中的變量叫類變量,而在類的方法中定義的變量叫實例變量。類的屬性包括成員變量和方法,其中方法的定義和普通函數的定義非常類似,但方法必須以self作爲第一個參數。
舉例:
>>>class MyFirstTestClass:
classSpec="itis a test class"
def__init__(self,word):
print"say "+word
defhello(self,name):
print"hello "+name
在Python類中定義的方法通常有三種:實例方法,類方法以及靜態方法。這三者之間的區別是實例方法一般都以self作爲第一個參數,必須和具體的對象實例進行綁定才能訪問,而類方法以cls作爲第一個參數,cls表示類本身,定義時使用@classmethod;而靜態方法不需要默認的任何參數,跟一般的普通函數類似.定義的時候使用@staticmethod。
>>>class MethodTest():
count= 0
defaddCount(self):
MethodTest.count+=1
print"I am an instance method,my count is"+str(MethodTest.count),self
@staticmethod
defstaticMethodAdd():
MethodTest.count+=1
print"I am a static methond,my count is"+str(MethodTest.count)
@classmethod
defclassMethodAdd(cls):
MethodTest.count+=1
print"I am a class method,my count is"+str(MethodTest.count),cls
>>>
>>>a=MethodTest()
>>>a.addCount()
Iam an instance method,my count is 1 <__main__.MethodTest instanceat 0x011EC990>
>>>MethodTest.addCount()
Traceback(most recent call last):
File"<pyshell#5>", line 1, in <module>
MethodTest.addCount()
TypeError:unbound method addCount() must be called with MethodTest instance asfirst argument (got nothing instead)
>>>a.staticMethodAdd()
Iam a static methond,my count is2
>>>MethodTest.staticMethodAdd()
Iam a static methond,my count is3
>>>a.classMethodAdd()
Iam a class method,my count is4 __main__.MethodTest
>>>MethodTest.classMethodAdd()
Iam a class method,my count is5 __main__.MethodTest
從上面的例子來看,靜態方法和類方法基本上區別不大,特別是有Java編程基礎的人會簡單的認爲靜態方法和類方法就是一回事,可是在Python中事實是這樣的嗎?看下面的例子:
>>>MethodTest.classMethodAdd()
Iam a class method,my count is5 __main__.MethodTest
>>>class subMethodTest(MethodTest):
pass
>>>b=subMethodTest()
>>>b.staticMethodAdd()
Iam a static methond,my count is6
>>>b.classMethodAdd()
Iam a class method,my count is7 __main__.subMethodTest
>>>a.classMethodAdd()
Iam a class method,my count is8 __main__.MethodTest
>>>
如果父類中定義有靜態方法a(),在子類中沒有覆蓋該方法的話,Sub.a()仍然指的是父類的a()方法。而如果a()是類方法的情況下,Sub.a()指向的是子類。@staticmethod只適用於不想定義全局函數的情況。
看看兩者的具體定義:
@staticmethod function is nothing morethan a function defined inside a class. It is callable withoutinstantiating the class first. It’s definition is immutable viainheritance.
@classmethod function also callablewithout instantiating the class, but its definition follows Subclass, not Parent class, via inheritance. That’s because the firstargument for @classmethod function must always be cls (class).
與Java不同,Python的訪問控制相對簡單,沒有public,private,protected等屬性,python認爲用戶在訪問對象的屬性的時候是明確自己在做什麼的,因此認爲私有數據不是必須的,但是如果你必須實現數據隱藏,也是可以的,具體方法就是在變量名前加雙下劃線。如__privatedata=0,定義私有方法則是在方法名稱前加上__下劃線。但即使對於隱藏的數據,也是有一定的方法可以訪問的。方法就是__className__attrName。Python對於私有變量會進行Namemangling是Python中爲了方便定義私有的變量和方法,防止和繼承類以及其他外部的變量或者方法衝突而採取的一種機制。在python中通過__spam定義的私有變量爲最終被翻譯成_classname__spam,其中classname爲類名,當類名是以_開頭的時候則不會發生Namemangling。Namemangling 存在的一個問題是當字符串長度超過255的時候則會發生截斷。
>>>class PrivateTest:
__myownedata=12
def__myownmethod(self):
print"can you see me?"
defsayhi(self):
print"say hi"
>>>class subPrivateTest(PrivateTest):
pass
>>>
>>>subPrivateTest.__myownedata
Traceback(most recent call last):
File"<pyshell#5>", line 1, in <module>
subPrivateTest.__myownedata
AttributeError:class subPrivateTest has no attribute '__myownedata'
>>>
>>>subPrivateTest._PrivateTest__myownedata
Python的構造函數有兩種,__init__和__new__,__init__的調用不會返回任何值,在繼承關係中,爲了保證父類實例正確的初始化,最好顯示的調用父類的__init__方法。與__init__不同,__new__實際是個類方法,以cls作爲第一個參數。
如果類中同時定義了__init__和__new__方法,則在創建對象的時候會優先使用__new__.
class A(object):
def __init__(self):
print("in init")
def __new__(self):
print("in new")
A()
如果__new__需要返回對象,則會默認調用__init__方法。利用new創建一個類的對象的最常用的方法爲:super(currentclass,cls).__new__(cls[, ...])
class A(object):
def __new__(cls):
Object = super(A,cls).__new__(cls)
print "in New"
return Object
def __init__(self):
print "in init"
class B(A):
def __init__(self):
print "in B's init"
B()
__new__構造函數會可變類的定製的時候非常有用,後面的小節中會體現。
Python由於具有垃圾回收機制,通常不需要用戶顯示的去調用析構函數,即使調用,實例也不會立即釋放,而是到該實例對象所有的引用都被清除掉後纔會執行。
>>>class P:
def__del__(self):
print"deleted"
>>>class S(P):
def__init__(self):
print'initialized'
def__del__(self):
P.__del__(self)
print"child deleted"
>>>a=S()
initialized
>>>b=a
>>>c=a
>>>id(a),id(b),id(c)
(18765704,18765704, 18765704)
>>>del a
>>>del b
>>>del c
deleted
childdeleted
>>>
在前面的例子中我們討論過類的實例方法必須通過實例調用,如果直接通過類去訪問會拋出異常,這種通過實例來訪問方法就叫綁定,調用的時候不需要顯示傳入self參數,而調用非綁定方法需要顯示傳入self參數,比如當子類繼承父類定義構造函數的時候,需要顯示調用父類的構造函數,但此時該方法並未與任何實例綁定,調用的時候需要使用superclassName.__init_(self)。
靜態方法可以直接被類或類實例調用。它沒有常規方法那樣的特殊行爲(綁定、非綁定、默認的第一個參數規則等等)。完全可以將靜態方法當成一個用屬性引用方式調用的普通函數來看待。任何時候定義靜態方法都不是必須的(靜態方法能實現的功能都可以通過定義一個普通函數來實現)
3. Python中的繼承
Python同時支持單繼承與多繼承,繼承的基本語法爲class新類名(父類1,父類2,..),當只有一個父類時爲單繼承,當存在多個父類時爲多繼承。子類會繼承父類的所有的屬性和方法,子類也可以覆蓋父類同名的變量和方法。在傳統類中,如果子類和父類中同名的方法或者屬性,在查找的時候基本遵循自左到右,深度優先的原則。如下列:
>>>class A:
defsayhi(self):
print'I am A hi'
>>>class B:
defsayhi(self):
print'I am B Hi'
>>>class C(A,B):
pass
>>>d=C()
>>>d.sayhi()
Iam A hi
>>>B.sayhi(d)
Iam B Hi
如果想調用父類B的sayhi方法則需要使用B.sayhi(d).而在python引入新式類後,在繼承關係中,方法和屬性的搜索有所改變,使用C3算法。具體將在MRO中詳細討論。
關於繼承的構造函數:
如果子類沒有定義自己的構造函數,父類的構造函數會被默認調用,但是此時如果要實例化子類的對象,則只能傳入父類的構造函數對應的參數,否則會出錯
classAddrBookEntry(object):
'addressbook entry class'
def__init__(self, nm, ph):
self.name= nm
self.phone= ph
print'Created instance for:', self.name
defupdatePhone(self, newph):
self.phone = newph
print'Updated phone# for:', self.name
classEmplAddrBookEntry(AddrBookEntry):
'EmployeeAddress Book Entry class'
defupdateEmail(self, newem):
self.email= newem
print'Updated e-mail address for:', self.name
john= EmplAddrBookEntry('John Doe', '408-555-1212')
printjohn.name
如果子類定義了自己的構造函數,而沒有顯示調用父類的構造函數,則父類的屬性不會被初始化
classAddrBookEntry(object):
'addressbook entry class'
def__init__(self, nm, ph):
self.name= nm
self.phone= ph
print'Created instance for:', self.name
defupdatePhone(self, newph):
self.phone = newph
print'Updated phone# for:', self.name
classEmplAddrBookEntry(AddrBookEntry):
'EmployeeAddress Book Entry class'
def__init__(self, nm, ph, id, em):
#AddrBookEntry.__init__(self, nm,ph)
self.empid= id
self.email= em
defupdateEmail(self, newem):
self.email= newem
print'Updated e-mail address for:', self.name
john= EmplAddrBookEntry('John Doe', '408-555-1212',42, '[email protected]')
printjohn.email
printjohn.empid
輸出:
42
Traceback(most recent call last):
printjohn.name
AttributeError:'EmplAddrBookEntry' object has no attribute 'name'
如果子類定義了自己的構造函數,顯示調用父類,子類和父類的屬性都會被初始化
classAddrBookEntry(object):
'addressbook entry class'
def__init__(self, nm, ph):
self.name= nm
self.phone= ph
print'Created instance for:', self.name
defupdatePhone(self, newph):
self.phone = newph
print'Updated phone# for:', self.name
classEmplAddrBookEntry(AddrBookEntry):
'EmployeeAddress Book Entry class'
def__init__(self, nm, ph, id, em):
AddrBookEntry.__init__(self, nm,ph)
self.empid= id
self.email= em
defupdateEmail(self, newem):
self.email= newem
print'Updated e-mail address for:', self.name
john= EmplAddrBookEntry('John Doe', '408-555-1212',42, '[email protected]')
printjohn.email
printjohn.empid
printjohn.name
MRO:即methodresolutionorder.簡單的說就是python針對多繼承查找一個屬性或者方法的一種算法。在引入新型類之前,MRO比較簡單,採取自左到右,深度優先的原則。比如有如下關係的類和屬性:
要查找對象x的attr屬性,其根據自左到右,深度優先的原則,其搜索順序爲D,B,A,C,位於樹結構底層的節點具有較高的level,當從高的level向低的level查找的時候遇到第一個屬性則不再繼續查找,因此上面的例子x的屬性值爲1.
>>> classA: attr=1
>>> classB(A):pass
>>> classC(A):attr=2
>>> classD(B,C):pass
>>> x=D()
>>> printx.attr
1
>>>
但按照多繼承的理解,level高的屬性應該覆蓋了level低的屬性,D同時繼承於B,C,而C是A的子類,那麼D的實例的屬性值理應爲attr=2而不是1,產生這個問題的主要原因是在繼承關係中產生了菱形,針對經典類的MRO算法有一定的侷限性,特別是在python2.2中加入了新型類後,由於object是所有對象之母,很容易形成菱形。因此python2.2採用改進的C3MRO算法進行搜索。
算法描述:
假設C1C2..CN表示類節點[C1,C2,...CN);
head=C1;
tail=C2...CN
C+(C1 C2..CN)=C C1C2...CN
若c繼承於B1,B2..BN,那麼在節點C的搜索順序L[C]=C加上其所有父節點的搜索順序和各個父節點的列表之和,也即
L[C(B1, ... , BN)]= C + merge(L[B1], ... ,L[BN], B1 ... BN)
其中merge的計算方法爲:
如果B1不在其它列表的tail中,則將其併入C的搜索列表中,同時將其從merge列表中移除,否則跳過改節點,繼續B2.。。如此重複知道merge爲空。
如果C是object對象或者沒有其他的父節點,則L[object]= object.。
對於單繼承,則L[C(B)]= C + merge(L[B],B) = C + L[B]
假設有如下繼承關係:
則:
L[O]= O
L[D]= D O
L[E]= E O
L[F]= F O
L[B]= B + merge(DO, EO, DE)
= B+D+merge(O,EO,E)
=B+D+merge(O,EO,E)
=B+D+E+merge(O,O)
=B D E O
L[A]= A + merge(BDEO,CDFO,BC)
=A + B + merge(DEO,CDFO,C)
=A + B + C + merge(DEO,DFO)
=A + B + C + D + merge(EO,FO)
=A + B + C + D + E + merge(O,FO)
=A + B + C + D + E + F + merge(O,O)
=A B C D E F O
針對上面的計算方法,利用Python的mro函數也可以說明該搜索順序:
>>>class F(object):pass
>>>class E(object):pass
>>>class D(object):pass
>>>class C(D,F):pass
>>>class B(D,E):pass
>>>class A(B,C): pass
>>>A.mro()
[<class'__main__.A'>, <class '__main__.B'>, <class'__main__.C'>, <class '__main__.D'>, <class'__main__.E'>, <class '__main__.F'>, <type 'object'>]
>>>B.mro()
[<class'__main__.B'>, <class '__main__.D'>, <class'__main__.E'>, <type 'object'>]
>>>
對於C3的MRO算法也可以簡單的理解爲:深度優先,從左到右遍歷基類,先遍歷高level的,再遍歷低level的,如果任何類在搜索中是重複的,只有最後一個出現的位置被保留,其餘會從MROlist中刪除。也就是說類的共同的祖先只有在其所有的子類都已經被check之後纔會check。對於A,其搜索順序應該是AB (D) (O) C D (O) E (O) F O
當然即使C3的MRO,也有其無法處理的情況,看下面的例子:
>>>class X(object):pass
>>>class Y(object):pass
>>>class A(X,Y):pass
>>>class B(Y,X):pass
>>>class C(A,B):
pass
Traceback(most recent call last):
File"<pyshell#124>", line 1, in <module>
classC(A,B):
TypeError:Error when calling the metaclass bases
Cannotcreate a consistent method resolution
order(MRO) for bases X, Y