前言
OOP的概念就不說(shuō)了,想必都很了解了。
Oop的三大特點(diǎn):繼承、封裝和多態(tài)。python作為動(dòng)態(tài)語(yǔ)言的一種,不僅實(shí)現(xiàn)了靜態(tài)語(yǔ)言的這三個(gè)特點(diǎn)的常規(guī)功能,也額外拓展了很多面向?qū)ο蟮男绿匦浴?/p>
new-style class
python2前幾個(gè)版本的時(shí)候,python還在用舊式類,現(xiàn)在已經(jīng)基本都是新式類了。兩者寫法上的區(qū)別
class Myclass:
class Myclass(object):
新式類是直接或間接繼承自object基類的,如果沒有可以明顯繼承的父類,那就在括號(hào)中加上object來(lái)聲明這是個(gè)新式類。相比舊式類,新式類最大的改動(dòng)就是在多繼承時(shí),到父類尋找本類中不存在的屬性時(shí),從以前的深度優(yōu)先搜索改為了廣度優(yōu)先搜索。這樣改的原因,也是跟新式類的類繼承的層級(jí)關(guān)系有關(guān)。對(duì)于Class A(B,C),如果仍用廣度優(yōu)先搜索,會(huì)從B一直向上遞歸搜索到object基類,這樣在繼承較多類時(shí),每次都會(huì)重復(fù)的去訪問object基類,很降低效率。
初次之外,新式類也增加一些原來(lái)舊式類沒有的新屬性,比如__slots__ __setattr__ __class__等等很實(shí)用的屬性。
封裝
在這里需要結(jié)合之前的python-命名空間和作用域那篇文章來(lái)解釋一下類對(duì)象的生效原理。
類在定義時(shí),會(huì)在當(dāng)前的局部作用域創(chuàng)建一個(gè)命名空間,也就是類名到類對(duì)象的映射,類中的屬性都要通過類名作為前綴來(lái)引用。
對(duì)于類中定義的變量,由類變量和實(shí)例變量的分別。類變量對(duì)于所有實(shí)例都可見,而單獨(dú)一個(gè)實(shí)例中的變量只對(duì)自己可見。
class Myclass(object):
a = 1
def __init__(self,b):
self.b = b
A = Myclass(2)
B = Myclass(3)
A.a #1
B.a #1
A.a = 100
B.a #100
A.b #2
B.b #3
類中定義的函數(shù),實(shí)例化對(duì)象中對(duì)用的函數(shù)我們都稱為方法。方法與普通函數(shù)的區(qū)別就是,方法的第一個(gè)參數(shù)都是 self ,比如 a.f(param) 這個(gè)方法的本質(zhì),就是將實(shí)例化出來(lái)的 a 對(duì)象和方法參數(shù)組裝成新的參數(shù)列表,來(lái)調(diào)用類中的函數(shù) A.f(a,param)
由__word__ 聲明的屬性,都是特殊變量或者方法。一般情況下,我么都用不上。上面例子的 __init__ 的作用是在類實(shí)例化時(shí),對(duì)實(shí)例進(jìn)行初始化的特殊方法,相當(dāng)于C++中的構(gòu)造函數(shù)。實(shí)際中,往往通過這個(gè)方法來(lái)給實(shí)例化傳遞參數(shù)。
看下面這個(gè)類:
class Student(object):
"""
test class
"""
kind = 'school'
def __init__(self,name,age):
self.name = name
self.age = age
def output(self):
print(self.name)
print(self.age)
def setage(self,age):
self.age = age
Allen = Student('Allen','22')
Peter = Student('Peter','23')
print(Allen.kind)
print(Peter.kind)
print(Allen.name,Allen.age)
print(Peter.name,Peter.age)
對(duì)于 Student 這個(gè)類,實(shí)例化了 Allen 和 Peter 兩個(gè)對(duì)象,擁有各自的 name age 屬性和共有的 kind 屬性。封裝的好處就是不用在類外關(guān)心具體的方法實(shí)現(xiàn),抽象出操作方法提供出去就可以。比如上個(gè)例子中的 output 方法,也可以通過 setage 來(lái)重新設(shè)置實(shí)例對(duì)象的 age 屬性。
當(dāng)然,在C++中有private和public變量的區(qū)分,目的也是為了保護(hù)某些比較重要的變量不被隨意或者無(wú)意的修改掉。python 在這方面也提供了訪問限制。
在類屬性前,添加單下劃線 _ 來(lái)聲明這個(gè)變量是private變量。如果你看到類屬性帶有下劃線,正確的做法是不要去使用它。但這全靠自覺!加載其他模塊時(shí),不會(huì)去加載帶 _ 聲明的變量和方法。python并不會(huì)真的去隱藏掉屬性,都是靠著這些約定俗成的規(guī)定來(lái)避免麻煩。
當(dāng)繼承關(guān)系比較深之后,對(duì)新類的屬性命名會(huì)很令人頭疼。因?yàn)槟阋紤]到新的命名會(huì)覆蓋掉不知道父類中的哪個(gè)屬性。遇到這種情況,可以在屬性前加上雙下劃線 __ ,python解釋器會(huì)將此類變量解析為 _classname__word,這樣前綴有著不同類的命名會(huì)避免覆蓋父類屬性的麻煩。比如上面的例子,將所有的self.name 改為 self.__name ,再通過 Allen.__name 去訪問時(shí),是會(huì)報(bào)錯(cuò)的,因?yàn)橐呀?jīng)被解釋器改掉了變量名。當(dāng)然可以在類內(nèi),添加 getage() 方法來(lái)訪問。類內(nèi)屬性的命名還是可以通過 self.__name 來(lái)訪問的。(雖然在類外,也可以通過Allen._Allen__name來(lái)強(qiáng)行訪問name屬性,但很不推薦!后果自負(fù))
繼承
繼承很簡(jiǎn)單,在class Myclass() 括號(hào)中,加上需要繼承的類就可以,與java的單一繼承不同,python是允許多繼承的!也就是class A(B,C) 這樣類A就同時(shí)繼承了類B和類C。當(dāng)然這邊有個(gè)問題,當(dāng)在調(diào)用類A中沒有的屬性時(shí),是去哪個(gè)父類尋找?具體可以看這篇文章,直接看例子:
class A:
def __init__(self):
pass
def save(self):
print("This is from A")
class B(A):
def __init__(self):
pass
class C(A):
def __init__(self):
pass
def save(self):
print("This is from C")
class D(B,C):
def __init__(self):
pass
fun = D()
fun.save()
很簡(jiǎn)單的小例子,如果來(lái)自A那就是深度優(yōu)先搜索,來(lái)自C的話就是廣度優(yōu)先搜索。當(dāng)然重寫父類已有的方法會(huì)覆蓋掉原有的實(shí)現(xiàn),這就可以通過上面提到的雙下劃線命名機(jī)制來(lái)避免沖突。子類可以通過 super() 來(lái)調(diào)用父類中的方法。
可以通過 isinstance(object,class) 來(lái)判斷對(duì)象是否是某類的實(shí)例。也可以通過issubclass(class1,class2)來(lái)判斷class1是否是class2的子類
多態(tài)
python中的多態(tài)很強(qiáng)大,它支持了 duck typing。先看具有繼承關(guān)系的類中的多態(tài):
class Human(object):
def out(self):
print("I live on Earth")
class Asian(Human):
def out(self):
print('I live in Asia')
class Chinese(Asian):
def out(self):
print('I live in China')
def run(person):
person.out()
A = Human()
B = Asian()
C = Chinese()
run(A)
run(B)
run(C)
結(jié)果:
I live on Earth
I live in Asia
I live in China
只定義了 run(person)這一個(gè)函數(shù),卻可以調(diào)用三個(gè)類中的方法,這就是多態(tài)的魅力。不關(guān)心傳入對(duì)象的具體信息,只需要你實(shí)現(xiàn)了out方法,就是可以被調(diào)用的。當(dāng)然對(duì)于有繼承關(guān)系的類,只要你的父類中有實(shí)現(xiàn),就可以被調(diào)用。
對(duì)于靜態(tài)語(yǔ)言,函數(shù)在定義時(shí)就已經(jīng)確定了,所以多態(tài)的威力還限制在只有繼承關(guān)系的類中。對(duì)于python這種動(dòng)態(tài)語(yǔ)言,只要你定義的對(duì)象擁有out()方法,甚至不需要你是函數(shù)定義參數(shù)的子類,就可以被調(diào)用。比如在上面例子中加入
class animals(object):
def out(self):
print('I am not person.')
輸出
I live on Earth
I live in Asia
I live in China
I am not person.
animals并不是Humen的子類,只是因?yàn)閷?shí)現(xiàn)了out()方法,就可以被run()函數(shù)調(diào)用。這就是著名的鴨子類型。動(dòng)態(tài)語(yǔ)言中,并不要求嚴(yán)格的繼承關(guān)系,只要這個(gè)對(duì)象 看起來(lái)像鴨子,走起路像鴨子,那就把它看成一只鴨子!
接觸過網(wǎng)絡(luò)編程的同學(xué),應(yīng)該很容易能聯(lián)想到網(wǎng)絡(luò)編程中也有相關(guān)的實(shí)現(xiàn)。比如讀寫文件,你會(huì)調(diào)用 open() read() write() 這些方法來(lái)操作file,但是還有很多特殊文件都可以通過這三個(gè)api來(lái)調(diào)用,比如socket描述符、pipe管道等等。這些東西都是長(zhǎng)的像文件,那就可以被這些api調(diào)用!