python - OOP基礎(chǔ)

前言

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í)例化了 AllenPeter 兩個(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)用!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 定義類并創(chuàng)建實(shí)例 在Python中,類通過 class 關(guān)鍵字定義。以 Person 為例,定義一個(gè)Person類...
    績(jī)重KF閱讀 4,096評(píng)論 0 13
  • 要點(diǎn): 函數(shù)式編程:注意不是“函數(shù)編程”,多了一個(gè)“式” 模塊:如何使用模塊 面向?qū)ο缶幊蹋好嫦驅(qū)ο蟮母拍睢傩浴?..
    victorsungo閱讀 1,694評(píng)論 0 6
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,628評(píng)論 18 399
  • Python進(jìn)階框架 希望大家喜歡,點(diǎn)贊哦首先感謝廖雪峰老師對(duì)于該課程的講解 一、函數(shù)式編程 1.1 函數(shù)式編程簡(jiǎn)...
    Gaolex閱讀 5,978評(píng)論 6 53
  • 靳會(huì)娟閱讀 568評(píng)論 0 0

友情鏈接更多精彩內(nèi)容