提到面向對象,老是離不開幾個重要的術語:多態(Polymorphism),繼承(Inheritance)和封裝(Encapsulation)。Python也是一種支持OOP的動態語言,本文將簡單闡述python對面向對象的支持。java
在討論Python的OOP以前,先看幾個OOP術語的定義:python
類:對具備相同數據和方法的一組對象的描述或定義。算法
對象:對象是一個類的實例。shell
實例(instance):一個對象的實例化實現。編程
標識(identity):每一個對象的實例都須要一個能夠惟一標識這個實例的標記。數據結構
實例屬性(instance attribute):一個對象就是一組屬性的集合。ide
實例方法(instance method):全部存取或者更新對象某個實例一條或者多條屬性的函數的集合。函數
類屬性(classattribute):屬於一個類中全部對象的屬性,不會只在某個實例上發生變化spa
類方法(classmethod):那些無須特定的對性實例就可以工做的從屬於類的函數。.net
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)。
靜態方法能夠直接被類或類實例調用。它沒有常規方法那樣的特殊行爲(綁定、非綁定、默認的第一個參數規則等等)。徹底能夠將靜態方法當成一個用屬性引用方式調用的普通函數來看待。任什麼時候候定義靜態方法都不是必須的(靜態方法能實現的功能均可以經過定義一個普通函數來實現)
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, 'john@spam.doe')
printjohn.email
printjohn.empid
輸出:
john@spam.doe
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, 'john@spam.doe')
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