Python 高級 12

property、魔法屬性和魔法方法、多重繼承和多繼承

1.5 property

學習目標

? 1. 能夠說出什么要使用 set/get 方法操作屬性

? 2. 能夠說出如何對屬性進行數據有效性控制

? 3. 能夠說出使用 set/get 方法的好處

? 4. 能夠說出說出 property 的作用

? 5. 能夠說出 property 類和 @property 有什么區別

? 6. 能夠說出語法糖的作用

--------------------------------------------------------------------------------

1.5.1 property 概述

property 本身的意義就是屬性、性質,在 python 中主要為屬性提供便利的操作方式。

1.5.2 思考

如果現在需要設計一個 銀行帳戶類 ,這個類中包含了帳戶人姓名和帳戶余額,不需要考慮具體的操作接口,你會怎么設計?

1.5.3 實現與復盤

簡單實現

class Account(object):

? ? def __init__(self, name, money):

? ? ? ? self.name = name? ? # 帳戶人姓名

? ? ? ? self.balance = money? ? # 帳戶余額

這樣的設計有什么問題? 很顯然,這樣設計起來很簡單方便,但是所有的屬性外部都能訪問修改,非常不安全。 如何改進呢?

改進一 隱藏實現細節

對于帳戶的信息,特別是金額,這是不能夠讓用戶直接修改的,如果要改變信息,需要窗口去辦理。

程序實現也是一樣,在使用對象時,盡量不要讓使用者直接操作對象中的屬性,這樣會帶來安全隱患。

改進辦法,使用私有屬性。

class Account(object):

def __init__(self, name, money):

self.__name = name? ? # 帳戶人姓名

self.__balance = money? ? # 帳戶余額

代碼改進以后,將所有的屬性都設計成了對象私有屬性,確實從外部在使用時,并不知道對象內部的屬性是什么,不能直接修改對象了,隱藏了實現的細節。

但是隨之又產生了另外一個問題,如果確實需要對這兩個屬性要進行修改怎么辦呢?

改進二 提供精確的訪問控制

在之前的學習中,學習過 set/get方法,是專門來為類的私有屬性提供訪問接口。

class Account(object):

? ? def __init__(self, name, money):

? ? ? ? self.__name = name? ? # 帳戶人姓名

? ? ? ? self.__balance = money? ? # 帳戶余額

? ? # 帳戶人姓名,在創建帳戶時就已經確定,不允許修改,所以對外不提供姓名的 set 方法

? ? def get_name(self):

? ? ? ? return self.__name

? ? def set_balance(self,money):

? ? ? ? self.__balance = money

? ? def get_balance(self):

? ? ? ? return self.__balance

經過修改,外部使用這個類的對象時,想使用對象中的屬性,只能通過類中提供的 set/get 接口來操作,提高了程序的安全性。

這樣,程序基本達到了設計需求,但是能不能更加完善呢?

如果在使用這個類的對象過程中,由于誤操作,傳入了不正常的數據,導致數據異常。該如何以避免這種情況發生呢?

比如:設置金額時出現了負數,或字符串,或其它類型的對象。

改進三 保證數據有效性

在 set 方法中,對傳入的數據進行判斷有效性,如果是無效數據,提示用戶出錯。

class Account(object):

? ? def __init__(self, name, money):

? ? ? ? self.__name = name? ? # 帳戶人姓名

? ? ? ? self.__balance = money? ? # 帳戶余額

? ? def get_name(self):

? ? ? ? return self.__name

? ? def set_balance(self,money):

? ? ? ? if isinstance(money, int):

? ? ? ? ? ? if money >= 0:

? ? ? ? ? ? ? ? self.__balance = money

? ? ? ? ? ? else:

? ? ? ? ? ? ? ? raise ValueError('輸入的金額不正確')

? ? ? ? else:

? ? ? ? ? ? raise ValueError('輸入的金額不是數字')

? ? def get_balance(self):

? ? ? ? return self.__balance

經過幾個版本的迭代,程序越來越健壯。安全性也越來越高。

但是在使用的過程中,能不能更加精練一些呢?即然 set/get 方法是用來操作屬性的方法,那么能不能以屬性操作的方式來使用呢?

答案是肯定的。

1.5.4 property 類

在 Python 中,提供了一個叫做 property 的類,通過對這個創建這個類的對象的設置,在使用對象的私有屬性時,可以不在使用屬性的函數的調用方式,而像普通的公有屬性一樣去使用屬性,為開發提供便利。

property(fget=None, fset=None, fdel=None, doc=None) -> property attribute

property 是一個類,init方法由四個參數組成,實例后返回一個用來操作屬性的對象 參數一:屬性的獲取方法 參數二:屬性的設置方法 參數三:屬性的刪除方法 參數四:屬性的描述

class Account(object):

? ? def __init__(self, name, money):

? ? ? ? self.__name = name? ? # 帳戶人姓名

? ? ? ? self.__balance = money? ? # 帳戶余額

? ? def __get_name(self):

? ? ? ? return self.__name

? ? def set_balance(self,money):

? ? ? ? if isinstance(money, int):

? ? ? ? ? ? if money >= 0:

? ? ? ? ? ? ? ? self.__balance = money

? ? ? ? ? ? else:

? ? ? ? ? ? ? ? raise ValueError('輸入的金額不正確')

? ? ? ? else:

? ? ? ? ? ? raise ValueError('輸入的金額不是數字')

? ? def get_balance(self):

? ? ? ? return self.__balance

? ? # 使用 property 類來為屬性設置便利的訪問方式

? ? name = property(__get_name)

? ? balance = property(get_balance, set_balance)

ac = Account('tom', 10)

print(ac.name)

print(ac.balance)

ac.balance = 1000

print(ac.balance)

通過 property 類實例對象以后,在使用對象中的屬性時,就可以像使用普通公有屬性一樣來調用,但是實際調用的還是 set/get 方法。 在實例 property 對象時,不是所有的參數都需要寫,比如示例中的 name 只提供了 get 方法,并且是一個私有的方法。這樣就完全隱藏了內部的實現細節 。

1.5.5 @property 裝飾器

Python 語法中,提供一種裝飾器語法,在函數定義的上一行,使用 @xxx 的形式來使用裝飾器。

裝飾器的作用就是提供裝飾的功能,在不改變原來函數功能的基礎上,添加新的功能。(裝飾器語法會在后面的課程中單獨講解)

這種形式被稱為語法糖。

語法糖指那些沒有給計算機語言添加新功能,而只是對人類來說更“甜蜜”的語法。 語法糖往往給程序員提供了更實用的編碼方式,有益于更好的編碼風格,更易讀。

利用 @property 裝飾器,可以用來簡化定義新的屬性或修改現有的操作。

class Account(object):

? ? def __init__(self, name, money):

? ? ? ? self.__name = name? ? # 帳戶人姓名

? ? ? ? self.__balance = money? ? # 帳戶余額

? ? # property 只能對獲取方法裝飾,并且獲取方法不需要再寫 get

? ? @property

? ? def name(self):

? ? ? ? return self.__name

? ? # 如果 property 裝飾的屬性還有 set 方法,需要寫到 get方法后定義

? ? @property

? ? def balance(self):

? ? ? ? return self.__balance

? ? # 實現 set 方法, 格式: @xxx.setter ,xxx 要和property裝飾的方法名一致

? ? @balance.setter

? ? def balance(self, money):

? ? ? ? if isinstance(money, int):

? ? ? ? ? ? if money >= 0:

? ? ? ? ? ? ? ? self.__balance = money

? ? ? ? ? ? else:

? ? ? ? ? ? ? ? raise ValueError('輸入的金額不正確')

? ? ? ? else:

? ? ? ? ? ? raise ValueError('輸入的金額不是數字')

ac = Account('tom', 10)

print(ac.name)

print(ac.balance)

ac.balance = 1000

print(ac.balance)

注意:

? 在使用 @property 裝飾屬性時,只能裝飾獲取方法

? @property 裝飾屬性時, set/get 方法不需要再屬性名前加 set 和 get ,直接寫屬性名即可

? 如果一個屬性同時有 set/get 方法,那么要先實現 @property 對獲取方法的定義

? 再實現設置方法的定義,定義時使用 @xxx.setter 裝飾,xxx 要和獲取方法名保持一致

1.5.5 總結

? 1. 在設計類時,盡量使用私有屬性,然后使用 set/get 接口來提供讀寫操作

? 2. 使用 set/get 接口方法,可以方便控制數據的有效性,精細化控制訪問權限,隱藏實現細節

? 3. 在定義 set/get 函數時,可以使用實例 property 類的對象,或 使用 @property 裝飾器來對方法進行處理

? 4. 處理之后的 set/get 函數在使用時,可以像直接使用屬性一樣進行操作,但實際調用還是函數,簡化操作

? 5. 一個類,一個是裝飾器

? 6. Python 中提供了很多語法糖,語法糖的作用是用來簡化操作,使代碼開發更簡單,是一種對開發人員‘更甜蜜’語法

1.6 魔法屬性和魔法方法

學習目標

? 1. 能夠說出什么是魔法方法

? 2. 能夠說出魔法屬性的作用

? 3. 能夠說出不同的魔法方法調用的時機

--------------------------------------------------------------------------------

1.6.1 魔法屬性和魔法方法概述

在 Python 中預先定義好了一些以 __xxx__ 形式的屬性和方法。

這些屬性和方法用來表示特定的意義和功能。

在程序運行過程中自動調用,或者根據開發的需求手動調用,甚至重寫魔法方法的功能。

本節主要介紹一些在開發中經常用到的魔法屬性和魔法方法。

1.6.2 __doc__ 屬性

Python 中有個特性叫做文檔字符串,即DocString,這個特性可以讓你的程序文檔更加清晰易懂。

DocString 通俗的說就是文件中的特殊注釋。用來描述文件,類,函數等的功能。

DocString 有固定的格式,一般是放在自己函數定義的第一行,用 ‘ ’ 符號指示,在這 ‘ ’ 里面添加函數功能說明就可以了。

DocString 可以使用 xxx.__doc__(注意前后都是雙_)屬性,將 DocString 特性 print 打印出來。print(print.__doc__)

AA.py

''' This is File DocString '''

def display():

? ? ''' This is Display Function DocString. '''

? ? pass

class Test(object):

? ? ''' This is Test Class DocString! '''

? ? pass

? ? def show(self):

? ? ? ? ''' This is Show Function DocString '''

? ? ? ? pass

BB.py

import AA

t = AA.Test()

print(t.__doc__)? ? ? ? ? ? # 對象使用描述是當前對象所屬類的描述

print(AA.Test.__doc__)? ? ? # 類的描述

print(t.show.__doc__)? ? ? # 對象方法的描述,查看方法描述時,方法名后面不能有括號

print(AA.display.__doc__)? # 公有函數的描述

print(AA.__doc__)? ? ? ? ? # 文件(模塊)的描述

Python的系統文件都在使用這個特性對功能進行描述。

print(print.__doc__)

調用的就是 print 函數的第一行描述內容

help函數 DocSting的典型用法是使用 help()調用。

當使用 help 函數時,help函數會通過__doc__魔法屬性將參數中的 DocString 屬性展示出來。

def display():

? ? ''' This is Display Function DocString. '''

? ? pass

help(display)

程序運行結果:

Help on function display in module __main__:

display()

? ? This is Display Function DocString.

1.6.3 __module__ 屬性 、 __class__ 屬性 、__bases__ 屬性 、 __mro__ 屬性

在 Python 程序開發過程中經常會導入很多其它模塊或者創建很多類的實例對象。

并且,Python 是一個支持多繼承和多重繼承的編程語言。

多繼承是指一個類同時繼承多個類。 多重繼承是指一個類所繼承的類之前還有繼承關系。

當使用模塊較多時,可以通過 __module__ 來查看當前成員屬于哪個模塊,通過 __class__ 屬性查看對象屬于哪個類

當類中有復雜的繼承關系時,可以通過 __bases__ 查看本類的父類有哪些,通過 __mro__ 屬性查看類中方法的查找順序。

AA.py

# 動物類

class Animal(object):

? ? pass

# 人類繼承動物類

class Person(Animal):

? ? pass

# 鳥類繼承動物類

class Bird(Animal):

? ? pass

# 鳥人類繼承人類和鳥類,即是多繼承,也是多重繼承

class BirdMan(Person, Bird):

? ? pass

# 顯示鳥人類的父類

print(BirdMan.__bases__)

# 顯示鳥人類初始化或實例讓用方法時的查找順序

print(BirdMan.__mro__)

BB.py

from AA import *

# 使用 module 查看當前類屬于哪個模塊

print(BirdMan.__module__)

# 使用 class 查看指定類的對象屬于哪個類

print(BirdMan().__class__)

# 使用 bases 來查看當前類的直接父類

print(BirdMan.__bases__)

# 使用 mro 來查看多重多繼承時的繼承關系

print(BirdMan.__mro__)

程序運行結果:

AA

<class 'AA.BirdMan'>

(<class 'AA.Person'>, <class 'AA.Bird'>)

(<class 'AA.BirdMan'>, <class 'AA.Person'>, <class 'AA.Bird'>, <class 'AA.Animal'>, <class 'object'>)

1.6.4 __new__ 方法 和 __init__ 方法

在 Python 中,__init__ 方法用來對實例中的屬性進行初始化操作,在使用類創建實例對象時,就會自動調用這方法。

但是 __init__ 方法并不是在創建對象時第一個被執行的方法。

在創建對象時,Pyhton 會先在內存中申請對象的內存空間。如果申請成功,說明對象才創建成功,之后才是使用 __init__ 進行初始化操作。

而申請對象空間這個過程就是由 __new__ 方法來執行的。

也就是說在創建對象過程中,會先執行 __new__ 在內存中申請實例存儲空間,然后再執行 __new__ 初始化實例對象空間。

可以通過下面的代碼驗證:

class A(object):

? ? def __new__(cls, *args, **kwargs):

? ? ? ? print('New Run...')

? ? ? ? return super().__new__(cls, *args, **kwargs)

? ? def __init__(self):

? ? ? ? print('Init Run ...')

a = A()

程序運行結果:

New Run...

Init Run ...

__new__ 方法在開辟完內存空間后,會自動調用 __init__ 方法來初始化對象空間。

開發過程中,一般不會重寫 __new__ 方法。一般都是重寫 __init__ 方法。

Python官方文檔的說法,__new__ 方法主要是當你繼承一些不可變的class時(比如int, str, tuple), 提供給你一個自定義這些類的實例化過程的途徑。 比如,要繼承 int 類,實現一個永遠是整數的類。 另外,在實現單例類的時候可以使用 __new__ 。

1.6.5 __call__ 方法

需求:記錄一個函數執行的次數

在程序設計過程中,并不建議使用全局變量,因為會大量占用內存空間不釋放,會破壞程序的封裝性。 而且全局變量在使用過程中,任何人都可以訪問,不安全,不符合面向對象的封裝思想。

這樣就可以使用 __call__ 方法來實現這個需求

實現__call__后,可以將對象當做函數一樣去使用,稱為仿函數或函數對象

那么普通函數和函數對象有什么區別?

普通對象在執行完成后就結束了,不能保存函數執行的狀態。而且在擴展其它函數時,函數間的關聯性不強。

函數對象可以保存函數的狀態。比如要實現對象調用的次數。而且可以在類中擴展更多的功能。

class MyFun(object):

? ? def __init__(self):

? ? ? ? self.__call_num = 0

? ? def __call__(self, *args, **kwargs):

? ? ? ? print('MyFunc Run...')

? ? ? ? self.__call_num += 1

? ? def get_call_num(self):

? ? ? ? return self.__call_num

mf = MyFun()

mf()

mf()

print(mf.get_call_num())

程序運行結果 :

MyFunc Run...

MyFunc Run...

2

1.6.6 __getitem__ 、__setitem__ 、__delitem__ 、__len__ 方法

魔術方法的作用:

__getitem__(self,key):返回鍵對應的值。

__setitem__(self,key,value):設置給定鍵的值

__delitem__(self,key):刪除給定鍵對應的元素。

__len__():返回元素的數量

當我們對類的屬性item進行下標的操作時,會被__getitem__()/__setitem__()/__delitem__()攔截,從而進行相應的操作,如賦值,修改內容,刪除內容等等。

如果現在要設計一個學生管理的類,實現學生信息的添加,獲取,刪除等操作,應該怎么設計?

但是我們發現,這個管理系統在管理學生時,實際使用的是一個字典,在管理學生信息的時候,能否不使用類提供的函數,而像使用字典一樣,直接通過操作字典的key的方式來直接操作呢?

類似如下操作: d = {} d['one'] = 1 print(d['one'])

程序改進

class StudentManager(object):

? ? '''學生信息管理'''

? ? def __init__(self):

? ? ? ? # 使用字典來保存所有的學生信息

? ? ? ? self.__students = {}

? ? # 添加學生

? ? def __setitem__(self, key, value):

? ? ? ? self.__students[key] = value

? ? # 獲取學生

? ? def __getitem__(self, item):

? ? ? ? if item not in self.__students:

? ? ? ? ? ? return None

? ? ? ? return self.__students[item]

? ? # 刪除學生

? ? def __delitem__(self, key):

? ? ? ? if key in self.__students:

? ? ? ? ? ? del self.__students[key]

? ? # 獲取學生人數

? ? def __len__(self):

? ? ? ? return len(self.__students)

# 創建學生管理對象

sm = StudentManager()

# 添加兩個學生

sm[1] = 'tom'

sm[2] = 'jack'

# 查看學生個數

print(len(sm))

# 顯示添加的學生

print(sm[1])

print(sm[2])

# 刪除2號學生

del sm[2]

# 查看學生個數

print(len(sm))

# 查看是否刪除

print(sm[1])

print(sm[2])

運行結果 :

2

tom

jack

1

tom

None

可以看出,結果完全相同,但是使用更加簡潔。這才是Python語法的精髓。

在自定義對象可以使用 對象[ ] 形式來直接操作對象中的容器,使代碼書寫更加簡潔,可讀性更高。

1.6.7 __str__ 方法

當使用print輸出對象的時候,只要自己在類中定義了__str__(self)方法,那么就會打印從在這個方法中return的數據

列表或字典在輸出時,通過直接打印列表或字典的名字,會直接打印出列表或字典中的內容。

那么自定義類會打印出來什么呢?

<__main__.StudentManager object="" at="" 0x103a120f0="">

那么自定義類,能不能像系統列表一樣,顯示所有的信息呢?

這個功能可以通過 __str__ 方法來實現

...

? ? def __str__(self):

? ? ? ? return '[' + " : ".join(self.__students.values()) + ']'

程序運行結果 :

[tom : jack]

小結:

__str__ 的作用是一個自定義的類型,在轉換成字符串類型時,程序是不知道怎么辦的的,因為程序也不知道你倒底寫了什么。

python提供 __str__ 這個方法,讓開發人員重寫這個方法,制定自定義對象轉換成字符串的規則。

1.6.8 總結

? 魔法屬性和魔法方法都是 Python 定義好的一些屬性或方法,用來實現一些特定的功能。

? 魔法屬性和魔法方法的主要作用是用來簡化 Python 的操作,在編寫代碼時讓使用方式更加簡潔。

1.7 多重繼承和多繼承

學習目標

? 1. 能夠說出什么是繼承

? 2. 能夠說出繼承的作用

? 3. 能夠說出多重繼承的初始化過程

? 4. 能夠說出多繼承的初始化過程

? 5. 什么是鉆石繼承

--------------------------------------------------------------------------------

1.7.1 繼承概述

在面向對象程序開發中,繼承是一個非常重要的概念。也是一個非常貼近現實繼承關系的一種思想。

通過繼承,可以讓子類去擁有父類的屬性和方法,而不必重新編寫相同的代碼,并且可以在父類的基礎上添加新的屬性和功能。

在繼承的同時,子類還可以重寫父類中的方法,從而獲取與父類不同的功能,實現多態。

在 Python 中 所有的類都是存在繼承關系的,沒有顯示繼承的,都是默認繼承 object 類

1.7.2 繼承的作用

? 子類在繼承父類的同時可以向子類中添加新的屬性和功能,提高開發效率,避免代碼冗余。

? 實現多態。

1.7.3 繼承回顧

單重單繼承

通過繼承,讓子類調用方法,如果子類存在就直接調用執行,如果沒有,就會到父類中去查找,如果父類中有就執行父類中的方法,如果沒有就再去object 類中去查找,如果父類中沒有就會報錯。

1.7.4 多重單繼承的初始化問題

在設計單類時,初始化數據時,只需要重寫__init__方法即可,可是當有多個類發生關系時,類中的屬性該何進行初始化呢?

在發生繼承時,子類會繼承父類的屬性的方法。

當在初始化時,子類只需要初始化自己擴展的屬性即可,父類中的屬性交給父類去初始化。

使用 父類名.__init__()的形式來調用父類的初始化方法

為什么要這么做呢?

? 父類中的屬性是私有的,子類根本不知道父類里的屬性是什么。

? 子類也不知道父類在初始化操作過程中做了哪些工作。

? 所以誰的內容就交給誰去執行是最安全的。

class Person():

? ? def __init__(self, name):

? ? ? ? self.__name = name

? ? ? ? print('Peron init...')

? ? def get_name(self):

? ? ? ? return self.__name

class Father(Person):

? ? def __init__(self,name, age):

? ? ? ? Person.__init__(self, name)

? ? ? ? self.__age = age

? ? ? ? print('Father init...')

? ? def get_age(self):

? ? ? ? return self.__age

class Son(Father):

? ? def __init__(self,name,age, gender):

? ? ? ? Father.__init__(self,name, age)

? ? ? ? self.__gender = gender

? ? ? ? print('Son init...')

? ? def get_gender(self):

? ? ? ? return self.__gender

s = Son('Tom', 18, '男')

print(s.get_name(),s.get_age(),s.get_gender())

程序執行結果:

Peron init...

Father init...

Son init...

Tom 18 男

1.7.5 多重多繼承的初始化問題

多重多繼承時使用 父類名.__init__()的形式來調用父類的初始化方法時,因為 Father 類和 Mother 類都是繼承于 Person 類的,在自己的初始化方法中,都執行了父類的初始化方法,所以Person 類的 __init__ 方法被執行了兩次。

? 多重多繼承的初始化使用super 函數

? super(類名, self).__init__(*args)

在 Python 中,提供 Super 函數來解決多繼承時父類被重復初始化這個問題。

super函數的格式

super(CurrentClassName, self).__init__(*args, **kwargs)

class Person():

? ? def __init__(self, name):

? ? ? ? self.__name = name

? ? ? ? print('Peron init...')

? ? def get_name(self):

? ? ? ? return self.__name

class Father(Person):

? ? def __init__(self, age, *args):

? ? ? ? super(Father, self).__init__(*args)

? ? ? ? self.__age = age

? ? ? ? print('Father init...')

? ? def get_age(self):

? ? ? ? return self.__age

class Mother(Person):

? ? def __init__(self, job,*args):

? ? ? ? super(Mother, self).__init__(*args)

? ? ? ? self.__job = job

? ? ? ? print('Mother init...')

? ? def get_job(self):

? ? ? ? return self.__job

class Son(Father, Mother):

? ? def __init__(self, name, age, gender, job):

? ? ? ? super(Son, self).__init__(age, job,name)

? ? ? ? self.__gender = gender

? ? ? ? print('Son init...')

? ? def get_gender(self):

? ? ? ? return self.__gender

s = Son('Tom', 18, '男','老師')

print(s.get_name(),s.get_age(),s.get_gender(),s.get_job())

程序執行結果:

Peron init...

Father init...

Mother init...

Son init...

Tom 18 男 老師

通過執行結果來看,使用 super 改進后的代碼只初始化了一次 Person 的初始化方法。

這種初始化方式稱為鉆石繼承(菱形繼承),如圖顯示 :

當程序使用了 super 函數以后,可以正常初始化,是以__mro__方法解析順序為依據。

1.7.6 __mro__

mro Method Resolution Order 方法解析順序

類名.__mro__返回一個元組類型的繼承鏈,來確定繼承的順序

在類出現繼承時,每個類中都會保存一個當前類的繼承關系的表。

super 函數在執行時,會在自己保存的這個繼承關系中去查找第一個參數,也就是當前的類名的下一個類是誰。然后去調用下個類的初始化方法。

Python 中使用廣度優先算法來決定繼承關系的排序。(廣度優先算法可自行查找資料了解)

廣度優先算法:橫向優先

深度優先算法:縱向優先

MRO順序

print(Son.__mro__)

程序執行結果 :

(<class '__main__.Son'>, <class '__main__.Mother'>, <class '__main__.Father'>, <class '__main__.Person'>, <class 'object'>)

1.7.7 super的簡化寫法

在初始化父類時,也可以使用 super().__init__() 函數來調用,簡化super函數的寫法。

這時,super函數中沒有參數,還是能正確執行,就是依照 __mro__ 中的順序來執行的。

class Person():

? ? def __init__(self, name):

? ? ? ? self.__name = name

? ? ? ? print('Peron init...')

? ? def get_name(self):

? ? ? ? return self.__name

class Mother(Person):

? ? def __init__(self, name, age, job):

? ? ? ? # super(Mother, self).__init__(name, age)

? ? ? ? super().__init__(name,age)

? ? ? ? self.__job = job

? ? ? ? print('Mother init...')

? ? def get_job(self):

? ? ? ? return self.__job

class Father(Person):

? ? def __init__(self,name, age):

? ? ? ? # super(Father, self).__init__(name)

? ? ? ? super().__init__(name)

? ? ? ? self.__age = age

? ? ? ? print('Father init...')

? ? def get_age(self):

? ? ? ? return self.__age

class Son(Mother, Father):

? ? def __init__(self, name, age, gender, job):

? ? ? ? # super(Son, self).__init__(name, age, job)

? ? ? ? super().__init__(name,age,job)

? ? ? ? self.__gender = gender

? ? ? ? print('Son init...')

? ? def get_gender(self):

? ? ? ? return self.__gender

s = Son('Tom', 18, '男','老師')

print(s.get_name(),s.get_age(),s.get_gender(),s.get_job())

程序執行結果:

Peron init...

Father init...

Mother init...

Son init...

Tom 18 男 老師

對象在調用方法時,也會依照這個順序來查找方法。

類在繼承時,繼承關系的書寫順序會影響 __mro__ 中的順序。

class A():

? ? pass

class B(A):

? ? pass

class C(A):

? ? pass

class D(B,C):

? ? pass

class E(C,B):

? ? pass

print(D.__mro__)

print(E.__mro__)

程序執行結果:

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

(<class '__main__.E'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

1.7.8 總結

? 繼承的思想是為了避免代碼的冗余,方便程序的擴展和復用

? 多重繼承是繼承關系中很多代(縱向)

? 多繼承是一個類可以同時繼承多個類(橫向)

? Python 中的類可以多重繼承,也可以多繼承

? Python 通過 __mro__ 來確定繼承的順序,使用的是廣度優先算法(橫向優先)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容