天天看點

python第七章_Python第七章-面向對象進階

面向對象進階

一、 特性

特性是指的property.

property這個詞的翻譯一直都有問題, 很多人把它翻譯為屬性, 其實是不恰當和不準确的. 在這裡翻譯成特性是為了和屬性差別開來.

屬性是指的attribute, 我們以前學習的執行個體變量和類變量是attribute, 是以也可以叫做執行個體屬性和類屬性.

property(特性)到底是個什麼東西?

我們前面學習類屬性和執行個體屬性的時候知道, 通路他們的時候就可以直接擷取到這些屬性的值.

而特性可以看成一種特殊的屬性, 為什麼呢?

但從通路方式來看, 特性和屬性看不出來差别, 但是特性實際上會經過計算之後再傳回值. 是以每一個特性都始終與一個方法相關聯.

1.1 定義特性

定義特性和定義執行個體方法類似, 隻需要另外在方法上面添加一個内置裝飾器:@property

通路特性和通路執行個體變量完全一樣, 不需要使用添加括号去調用.

import math

class Circle:

def __init__(self, r):

self.r = r

@property

def area(self):

"""

定義特性

這個特性是計算出來圓的面積

:return:

"""

return math.pi * (self.r ** 2)

c = Circle(10)

print(c.area)

python第七章_Python第七章-面向對象進階

很明顯, 特性背後的本質是一個方法的存在, 是以你不可能在外面去修改這個特性的值!

試圖修改特性的值隻會抛出一個異常.

c.area = 100

python第七章_Python第七章-面向對象進階

1.2 使用特性的設計哲學

這種特性使用方式遵循所謂的 統一通路原則.

實際上, 定義一個類總是保持接口的統一總是好的.

有了特性, 把通路屬性和通路方法統一了, 都像在通路屬性一樣, 省得去考慮到底什麼時候需要添加括号,什麼時候不用添加括号.

1.3 特性的攔截操作

python 還提供了設定和删除屬性.

通過給方法添加其他内置裝飾器來實作

設定:@特性名.setter

删除:@特性名.deleter

class Student:

def __init__(self, name):

self._name = name # name 是特性了, 是以用執行個體變量存儲特性的值的是換個變量名!!!

@property

def name(self):

return self._name

@name.setter

def name(self, name):

if type(name) is str and len(name) > 2:

self._name = name

else:

print("你提供的值" + str(name) + "不合法!")

@name.deleter

def name(self):

print("對不起, name 不允許删除")

s = Student("李四")

print(s.name)

s.name = "彩霞"

print(s.name)

s.name = "張三"

print(s.name)

del s.name

二、三大特性之一-封裝性

面向對象的三大特征:封裝, 繼承, 多态

2.1什麼是封裝性

1.封裝是面向對象程式設計的一大特點

2.面向對象程式設計的第一步,就是講屬性和方法封裝到一個抽象的類中

3.外界使用類建立對象,然後讓對象調用方法

4.對象方法的細節都被封裝在類的内部

在類中定義屬性, 定義方法就是在封裝資料和代碼.

2.2 私有化屬性

首先先明确一點, python 不能真正的對屬性(和方法)進行私有, 因為 python 沒有想 java 那樣的private可用.

python 提供的"私有", 是為了怕在程式設計的過程中對對象屬性不小心"誤傷"提供的一種保護機制! 這種級别的私有稍微隻要知道了規則, 是很容易通路到所謂的私有屬性或方法的.

2.2.1 為什麼需要私有

封裝和保護資料的需要.

預設情況下, 類的所有屬性和方法都是公共的, 也就意味着對他們的通路沒有做任何的限制.

意味着, 在基類中定義的所有内容都可以都會被派生類繼承, 并可從派生類内部進行通路.

在面向對象的應用程式設計中, 我們通常不希望這種行為, 因為他們暴露基類的内部實作, 可能導緻派生類中的使用的私有名稱與基類中使用的相同的私有名稱發生沖突.

屬性或方法私有後就可以避免這種問題!

2.2.2 "私有"機制

為了解決前面說的問題, python 提供了一種叫做名稱改寫(name mangling)的機制

如果給屬性或者方法命名的時候, 使用兩個下劃線開頭(__)的屬性和方法名會自動變形為_類名__方法名, 這樣就避免了在基礎中命名沖突的問題.

class Student:

def __init__(self):

pass

def __say(self):

print("我是私有方法你信嗎?")

s = Student()

s.__say() # 雙下劃線開頭的方法已經被形變, 此處通路不到

python第七章_Python第七章-面向對象進階

s._Student__say()

python第七章_Python第七章-面向對象進階

2.2.3 不是真正的私有

盡管這種方案隐藏了資料, 但是并沒有提供嚴格的機制來限制對私有屬性和方法的通路.

雖然這種機制好像多了一層處理, 但是這種變形是發生在類的定義期間, 并不會在方法執行期間發生, 是以并沒有添加額外的開銷.

2.2.4 不同的聲音

有部分人認為這種使用雙__的機制好辣雞, 寫兩個下劃線影響效率. 他們使用一個下劃線, 并把這個作為一個約定.

好吧, 你喜歡哪種呢?

三、面向對象三大特性-繼承性(Inheritance)

這一節我們來學習面向的對象的再一個特征: 繼承

python第七章_Python第七章-面向對象進階

3.1繼承性的概念

繼承(extends)是建立新類的一種機制, 目的是專門使用和修改先有類的行為.

原有類稱為超類(super class), 基類(base class)或父類.

新類稱為子類或派生類.

通過繼承建立類時, 所建立的類将繼承其基類所有的屬性和方法, 派生類也可以重新定義任何這些屬性和方法, 并添加自己的新屬性和方法

python第七章_Python第七章-面向對象進階

3.2 繼承性的意義

繼承實作代碼的重用,相同的代碼不需要重複的編寫

從子類的角度來看,避免了重複的代碼。(子類繼承父類後,子類可以直接使用父類的屬性和方法)

從父類的角度來看,子類擴充了父類的功能。(因為子類也是一個特殊的父類)

子類可以直接通路父類的屬性和方法。

子類可以新增自己的屬性和方法。

子類可以重寫父類的方法。

3.3 繼承的文法和具體實作

繼承的文法如下:

class 父類名:

pass

class 子類名(父類名):

pass

3.3.1最簡單的繼承

python 的繼承是在類名的後面添加括号, 然後在括号中聲明要繼承的父類.

class Father:

def speak(self):

print("我是父類中的 speak 方法")

# Son繼承 Father 類

class Son(Father):

pass

s = Son()

s.speak()

python第七章_Python第七章-面向對象進階

說明:

從字面上我們看到Son沒有定義任何的方法, 但是由于Son繼承自Father, 則Son會繼承Father的所有屬性和方法

調用方法時, 方法的查找規則: 先在目前類中查找, 目前類找不到想要的方法, 則去父類中查找, 還找不到然後繼續向上查找. 一旦找到則立即執行. 如果找到最頂層還找不到, 則會抛出異常

示例代碼

# 建立人類

class Person:

# 定義吃東西方法

def eat(self):

print("吃窩窩頭。。")

# 定義睡覺方法

def sleep(self):

print("睡着啦。。")

# 建立學生類

class Student(Person):

# 子類新增方法:學習

def study(self):

print("學生學習啦。。。把你爸樂壞了。。。。。")

# 建立父類對象,通路父類的方法

zhangsan = Person();

zhangsan.eat()

zhangsan.sleep()

# 建立子類對象,通路父類的方法和子類的方法

ergou = Student();

ergou.eat() # 通路父類的方法

ergou.sleep() # 通路父類的方法

ergou.study() # 通路子類的新增方法

3.3.2 繼承中的__init__()的調用規則

如果子類沒有手動__init__()方法, 則 python 自動調用子類的__init__()的時候, 也會自動的調用基類的__init()__方法.

class Father:

def __init__(self):

print("基類的 init ")

# Son繼承 Father 類

class Son(Father):

def speak(self):

pass

s = Son()

python第七章_Python第七章-面向對象進階

如果子類手動添加了__init__(), 則 python 不會再自動的去調用基類的__init__()

class Father:

def __init__(self):

print("基類的 init ")

# Son繼承 Father 類

class Son(Father):

def __init__(self):

print("子類的 init ")

def speak(self):

pass

s = Son()

python第七章_Python第七章-面向對象進階

如果想通過基類初始化一些資料, 則必須顯示的調用這個方法, 調用文法是:

基類名.__init__(self, 參數...)

class Father:

def __init__(self, name):

print("基類的 init ")

self.name = name

def speak(self):

print("我是父類中的 speak 方法" + self.name)

# Son繼承 Father 類

class Son(Father):

def __init__(self, name, age):

# name 屬性的初始化應該交給基類去完成, 手動調用基類的方法. 一般放在首行

Father.__init__(self, name) # 調動指定類的方法, 并手動綁定這個方法的 self

print("子類的 init ")

self.age = age

s = Son("李四", 20)

s.speak()

print(s.name)

print(s.age)

python第七章_Python第七章-面向對象進階

3.4方法的重寫(override)

3.4.1重寫的概念

我們已經了解了調用方法時候的查找規則, 先在子類中查找, 子類查找不到再去父類中查找.

如果父類的方法不滿足子類的需求, 利用這個查找規則, 我們就可以在子類中添加一個與父類的一樣的方法, 那麼以後就會直接執行子類的方法, 而不會再去父類中查找.

這就叫方法的覆寫.(override)

>重寫,就是子類将父類已有的方法重新實作。

父類封裝的方法,不能滿足子類的需求,子類可以重寫父類的方法。在調用時,調用的是重寫的方法,而不會調用父類封裝的方法。

3.4.2重寫父類方法的兩種情況

覆寫父類的方法

父類的方法實作和子類的方法實作,完全不同,子類可以重新編寫父類的方法實作。

具體的實作方式,就相當于在子類中定義了一個和父類同名的方法并且實作

對父類方法進行擴充

子類的方法實作中包含父類的方法實作。(也就是說,父類原本封裝的方法實作是子類方法的一部分)。

在子類中重寫父類的方法

在需要的位置使用super().父類方法來調用父類的方法

代碼其他的位置針對子類的需求,編寫子類特有的代碼實作。

如果在覆寫的方法中, 子類還需要執行父類的方法, 則可以手動調用父類的方法:

父類名.方法(self, 參數...)

class Father:

def __init__(self, name):

self.name = name

def speak(self):

print("我是父類中的 speak 方法" + self.name)

# Son繼承 Father 類

class Son(Father):

def __init__(self, name, age):

Father.__init__(self, name)

self.age = age

# 子類中覆寫了父類的方法

def speak(self):

Father.speak(self)

print("我是子類的 speak 方法" + self.name + " 年齡:" + str(self.age))

s = Son("李四", 20)

s.speak()

python第七章_Python第七章-面向對象進階

3.4.3關于super

在Python中super是一個特殊的類(Python 3.x以後出現)

super()就是使用super類建立出來的對象

最常使用的場景就是在重寫父類方法時,調用在父類中封裝的方法實作

3.5、父類的私有屬性和方法

子類對象不能在自己的方法内部,直接通路父類的私有屬性或私有方法

子類對象可以通過父類的共有方法間接通路到私有屬性或私有方法

私有屬性和方法是對象的隐私,不對外公開,外界以及子類都不能直接通路

私有屬性和方法通常用于做一些内部的事情

3.6、多繼承

3.6.1多繼承的概念

多繼承:子類可以擁有多個父類,并且具有所有父類的屬性和方法

​比如:孩子會繼承自己的父親和母親的特性

python第七章_Python第七章-面向對象進階

3.6.2多繼承的文法

class 子類名(父類名1, 父類名2...):

pass

示例代碼:

# 父類A

class A:

def test1(self):

print("A類中的test1方法。。")

# 父類B

class B:

def test2(self):

print("B類中的test2方法。。")

# 子類C同時繼承A和B

class C(A,B):

pass

# 建立C對象

c1 = C()

c1.test1()

c1.test2()

3.6.3多繼承的注意事項

提問:如果不同的父類中存在同名的方法,子類對象在調用方法時,會調用哪一個父類中的方法呢?

開發時,應該盡量避免這種容易産生混淆的情況。如果父類之間存在同名的屬性或者方法,應該盡量避免使用多繼承

3.6.4 Python中的 MRO (方法搜尋順序)[擴充]

python中針對類提供了一個内置屬性,___mro__可以檢視方法搜尋順序

MRO是method resolution order,主要用于在多繼承時判斷方法,屬性的調用路徑

print(C.__mro__)

輸出結果:

(, , , )

在搜尋方法時,是按照__mro_-的輸出結果從左至右的順序查找

如果目前類中找到方法,就直接執行,不再搜尋

如果沒有找到,就查找下一個類中是否有對應的方法,如果找到,就直接執行,不再搜尋

如果找到最後一個雷,還沒有對應的方法,程式報錯

3.6.5 python 中的上帝類型

python 中有個類比較特殊, 所有的類都直接和間接的繼承自這個類.

這個類就是:object. 他是所有類的基類.

如果一個類沒有顯示的去繼承一個類, 則這個類預設就繼承object, 也可以去顯示的繼承這個類.

class Student(object):

pass

3.6.6 新式類和舊式(經典)類[擴充]

object是python為所有對象提供的基類,提供有一些内置的屬性和方法,可以使用

dir函數檢視

新式類:以object為基類的類,推薦使用

經典類:不以object為基類的類,不推薦使用

在python 3.x中定義類時,如果沒有指定父類,會預設使用object作為該類的父類。是以python 3.x中定義的類都是新式類

在python 2.x中定義類時,如果沒有指定父類,則不會以object作為父類

新式類和經典類在多繼承時,會影響到方法的搜尋順序

提示:為了保證編寫的代碼能夠同時在python 2.x 和python 3.x 運作,在定義類的時候,如果沒有父類,建議統一繼承自object

class 類名(object):

pass

四、面向對象三大特性-多态性(Polymorphism)

4.1多态性的概念

封裝性,根據職責将屬性和方法封裝到一個抽象的類中

​定義類的準則

繼承性,實作代碼的重用,相同的代碼不需要重複的編寫

​設計類的技巧

​子類針對自己特有的書需求,編寫特定的代碼

多态性,不同的子類對象,調用相同的父類方法,産生不同的執行結果

​多态可以增加代碼的靈活性

​以繼承和重寫父類方法為前提

​是調用方法的技巧,不會影響到類的内部設計

python第七章_Python第七章-面向對象進階

示例代碼:

"""

多态性:

繼承和重寫為前提,建立不同的對象執行的具體方法不同

"""

class Father(object):

def __init__(self, name):

print('父類的init方法')

self.name = name

def say(self):

print('父類的say方法' + self.name)

# Son類繼承于Father類,python中是類繼承于類的

class Son(Father):

def __init__(self, name, age):

Father.__init__(self, name)

self.age = age

print('子類的init方法')

def say(self):

Father.say(self)

print('子類的say方法:' + self.name + ',' + str(self.age))

# 以下程式會展現出多态性

def mytest(obj):

obj.say()

f1 = Father("張爸爸")

mytest(f1)

print("---------------")

f2 = Son("小頭兒子",5)

mytest(f2)

4.2屬性和方法查找順序

多态性(多态綁定)是在有繼承背景情況下使用的一種特性.

是指在不考慮執行個體背景的情況下使用執行個體

多态的理論根據是屬性和方法的查找過程. 隻要使用obj.attr的方式使用屬性和方法, 則查找順序一定是: 對象本身, 類定義, 基類定義...

關于先查找對象本身的說明: 因為 python 是一門動态語言, 允許我們在代碼執行的過程中去動态的給對象添加屬性和方法, 是以先從對象本身查找.

class Father:

def __init__(self, name):

self.name = name

def speak(self):

print("我是父類中的 speak 方法" + self.name)

# Son繼承 Father 類

class Son(Father):

def __init__(self, name, age):

Father.__init__(self, name)

self.age = age

def speak(self):

Father.speak(self)

print("我是子類的 speak 方法" + self.name + " 年齡:" + str(self.age))

def foo():

print("我是動态添加上去的...")

s = Son("李四", 20)

s.speak = foo

s.speak()

python第七章_Python第七章-面向對象進階

4.3 鴨子類型

python 的多态有的時候很多人把它稱之為鴨子類型

鴨子類型是指: 看起來像鴨子, 叫起來像鴨子, 走起來像鴨子, 那麼它既是鴨子, 你就可以把它當鴨子來用.

換成程式設計語言的說法就是: 對象屬性和方法的時候完成時和類型分開的.

class A:

def speak(self):

print("a 類中的方法")

class B:

def speak(self):

print("b 類中的方法")

def foo(obj):

obj.speak()

a = A()

b = B()

foo(a)

foo(b)

python第七章_Python第七章-面向對象進階

說明:

foo接受一個對象, 隻要這個對象中有speak()方法, 就可以正常執行, 我們并不關注他的類型

A, B這兩個類沒有任何的關系, 但是他們都有speak方法, 是以傳遞過去都沒有任何的問題.

這就是鴨子模型, 隻要你看起來有speak就可以了

五、其他

5.1 特殊屬性__slot__

5.1.1動态添加屬性的問題

通過前面的學習中我們知道, 由于 python 的動态語言的特性, 我們可以動态的給對象添加屬性和方法.

但是這種方式添加的屬性和方法, 隻在目前對象上有用, 在其他對象上是沒用.

class A:

pass

a1 = A()

a1.name = "李四" #給 a1 對象添加一個屬性

print(a1.name)

a2 = A()

print(a2.name) # a2中沒有 name 屬性, 是以抛異常

python第七章_Python第七章-面向對象進階

5.1.2 __slot__的基本使用

添加屬性和方法最好直接在類中添加, 這樣所有的對象都可以擁有了.

如果我想避免把某些屬性直接添加到執行個體對象上, 可以使用一個特殊屬性:__slot__類實作.

給__slot__定義一個元組, 則元組内的屬性名允許在執行個體對象上直接添加, 其他的都不允許.

class A:

__slots__ = ("name", )

a1 = A()

a1.name = "李四" # 給 a1 對象添加一個屬性 name 屬性是允許的

print(a1.name)

a1.age = 20 # age 不允許, 是以抛異常

print(a1.age)

python第七章_Python第七章-面向對象進階

注意:

我們的__init__()中添加屬性是在self上添加的, 其實也是直接在對象上添加, 是以沒有在元組中的屬性名, 也是不允許的.

對于我們直接在類中添加方法是沒有任何的影響的.

class A:

__slots__ = ("name",)

def __init__(self):

self.age = 30 # 也是不允許的

a = A()

python第七章_Python第七章-面向對象進階

5.1.3 繼承中的__slot__

__slot__隻對目前類有用, 對他的子類不起作用. 是以子類也要有自己的__slot__

class A:

__slots__ = ("name",)

def __init__(self):

self.age = 30 # 也是不允許的

class B:

def __init__(self):

self.age = 30

b = B()

print(b.age)

python第七章_Python第七章-面向對象進階

5.1.4 __slot__對性能上的提升

一些人把__slot__作為一種安全的特性來實作, 然後實際上他對記憶體和執行速度上的性能優化才是最重要的.

不使用__slot__, python 使用字典的方式去存儲執行個體資料的, 如果一個程式使用大量的執行個體, 測記憶體占用和執行效率都會影響比較大.

使用__slot__後, python 存儲執行個體資料的時候, 不再使用字典, 而是使用一種更加高效的基于數組的資料結構. 可以顯著減少記憶體占用和執行時間.

5.2 執行個體的測試類型

任何一個類都可以做為類型!

建立類的執行個體時, 該執行個體的類型是這個類本身, 如果有繼承存在, 則父類型也是這個執行個體的類型.

有些情況下, 我們需要先測試執行個體的類型然後再寫相應的代碼.

python 支援 2 種測試方法:

5.2.1 内置函數:type(執行個體)

class A:

pass

class B(A):

pass

class C:

pass

a = A()

b = B()

c = C()

print(type(a))

print(type(b))

print(type(c))

python第七章_Python第七章-面向對象進階

說明:

type傳回的是這個執行個體的所屬類的類對象.

補充一下:

其實我們經常接觸到的有兩種對象:1. 執行個體對象 2. 類對象

類對象就是: 表示類本身的那個對象!

5.2.2 内置函數:isinstance(執行個體, 類型)

class A:

pass

class B(A):

pass

class C:

pass

a = A()

b = B()

c = C()

print(isinstance(a, A)) # True

print(isinstance(b, B)) # True

print(isinstance(b, A)) # True 繼承關系

print(isinstance(c, C)) # True

print(isinstance(c, A)) # False

說明:

這個函數傳回的是布爾值, 使用起來友善, 是以以後測試類型建議用這個函數

這個函數繼承關系也可以測試出來. b是B類建立出來的, B繼承自A, 是以b也算是類A的執行個體.

對一個執行個體也可以同時測試多個類型, 有一個滿足就傳回True, isinstance(執行個體, (類 a, 類 b, ...))). 需要把多個類封裝到一個tuple中.

print(isinstance(c, (A, B, C))) # True

5.2.3 類與類的關系: issubclass(類1, 類2)

用來測試類1是不是類2的子類.

class A:

pass

class B(A):

pass

class C:

pass

print(issubclass(B, A)) # True

print(issubclass(C, A)) # False

print(issubclass(A, B)) # False

print(issubclass(A, object)) # True

print(issubclass(C, (object, A))) # True 第二個參數也是可以 class 組成的元組