property鹅经、魔法屬性和魔法方法、多重繼承和多繼承
1.5 property
學習目標
? 1. 能夠說出什么要使用 set/get 方法操作屬性
? 2. 能夠說出如何對屬性進行數據有效性控制
? 3. 能夠說出使用 set/get 方法的好處
? 4. 能夠說出說出 property 的作用
? 5. 能夠說出 property 類和 @property 有什么區(qū)別
? 6. 能夠說出語法糖的作用
--------------------------------------------------------------------------------
1.5.1 property 概述
property 本身的意義就是屬性坯汤、性質,在 python 中主要為屬性提供便利的操作方式搀愧。
1.5.2 思考
如果現(xiàn)在需要設計一個 銀行帳戶類 惰聂,這個類中包含了帳戶人姓名和帳戶余額,不需要考慮具體的操作接口咱筛,你會怎么設計搓幌?
1.5.3 實現(xiàn)與復盤
簡單實現(xiàn)
class Account(object):
? ? def __init__(self, name, money):
? ? ? ? self.name = name? ? # 帳戶人姓名
? ? ? ? self.balance = money? ? # 帳戶余額
這樣的設計有什么問題? 很顯然迅箩,這樣設計起來很簡單方便溉愁,但是所有的屬性外部都能訪問修改,非常不安全饲趋。 如何改進呢拐揭?
改進一 隱藏實現(xiàn)細節(jié)
對于帳戶的信息撤蟆,特別是金額,這是不能夠讓用戶直接修改的堂污,如果要改變信息家肯,需要窗口去辦理。
程序實現(xiàn)也是一樣盟猖,在使用對象時讨衣,盡量不要讓使用者直接操作對象中的屬性,這樣會帶來安全隱患式镐。
改進辦法反镇,使用私有屬性。
class Account(object):
def __init__(self, name, money):
self.__name = name? ? # 帳戶人姓名
self.__balance = money? ? # 帳戶余額
代碼改進以后碟案,將所有的屬性都設計成了對象私有屬性愿险,確實從外部在使用時,并不知道對象內部的屬性是什么价说,不能直接修改對象了辆亏,隱藏了實現(xiàn)的細節(jié)。
但是隨之又產生了另外一個問題鳖目,如果確實需要對這兩個屬性要進行修改怎么辦呢扮叨?
改進二 提供精確的訪問控制
在之前的學習中,學習過 set/get方法领迈,是專門來為類的私有屬性提供訪問接口彻磁。
class Account(object):
? ? def __init__(self, name, money):
? ? ? ? self.__name = name? ? # 帳戶人姓名
? ? ? ? self.__balance = money? ? # 帳戶余額
? ? # 帳戶人姓名,在創(chuàng)建帳戶時就已經確定狸捅,不允許修改衷蜓,所以對外不提供姓名的 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 接口來操作,提高了程序的安全性朽褪。
這樣置吓,程序基本達到了設計需求,但是能不能更加完善呢缔赠?
如果在使用這個類的對象過程中衍锚,由于誤操作,傳入了不正常的數據嗤堰,導致數據異常戴质。該如何以避免這種情況發(fā)生呢?
比如:設置金額時出現(xiàn)了負數,或字符串置森,或其它類型的對象斗埂。
改進三 保證數據有效性
在 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 的類橄浓,通過對這個創(chuàng)建這個類的對象的設置,在使用對象的私有屬性時亮航,可以不在使用屬性的函數的調用方式荸实,而像普通的公有屬性一樣去使用屬性,為開發(fā)提供便利缴淋。
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 方法讹剔,并且是一個私有的方法油讯。這樣就完全隱藏了內部的實現(xiàn)細節(jié) 。
1.5.5 @property 裝飾器
Python 語法中延欠,提供一種裝飾器語法陌兑,在函數定義的上一行,使用 @xxx 的形式來使用裝飾器由捎。
裝飾器的作用就是提供裝飾的功能兔综,在不改變原來函數功能的基礎上,添加新的功能。(裝飾器語法會在后面的課程中單獨講解)
這種形式被稱為語法糖软驰。
語法糖指那些沒有給計算機語言添加新功能涧窒,而只是對人類來說更“甜蜜”的語法。 語法糖往往給程序員提供了更實用的編碼方式锭亏,有益于更好的編碼風格纠吴,更易讀。
利用 @property 裝飾器慧瘤,可以用來簡化定義新的屬性或修改現(xiàn)有的操作戴已。
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
? ? # 實現(xiàn) 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 方法握联,那么要先實現(xiàn) @property 對獲取方法的定義
? 再實現(xiàn)設置方法的定義,定義時使用 @xxx.setter 裝飾每瞒,xxx 要和獲取方法名保持一致
1.5.5 總結
? 1. 在設計類時金闽,盡量使用私有屬性,然后使用 set/get 接口來提供讀寫操作
? 2. 使用 set/get 接口方法独泞,可以方便控制數據的有效性呐矾,精細化控制訪問權限,隱藏實現(xiàn)細節(jié)
? 3. 在定義 set/get 函數時懦砂,可以使用實例 property 類的對象蜒犯,或 使用 @property 裝飾器來對方法進行處理
? 4. 處理之后的 set/get 函數在使用時,可以像直接使用屬性一樣進行操作荞膘,但實際調用還是函數罚随,簡化操作
? 5. 一個類,一個是裝飾器
? 6. Python 中提供了很多語法糖羽资,語法糖的作用是用來簡化操作淘菩,使代碼開發(fā)更簡單,是一種對開發(fā)人員‘更甜蜜’語法
1.6 魔法屬性和魔法方法
學習目標
? 1. 能夠說出什么是魔法方法
? 2. 能夠說出魔法屬性的作用
? 3. 能夠說出不同的魔法方法調用的時機
--------------------------------------------------------------------------------
1.6.1 魔法屬性和魔法方法概述
在 Python 中預先定義好了一些以 __xxx__ 形式的屬性和方法屠升。
這些屬性和方法用來表示特定的意義和功能潮改。
在程序運行過程中自動調用,或者根據開發(fā)的需求手動調用腹暖,甚至重寫魔法方法的功能汇在。
本節(jié)主要介紹一些在開發(fā)中經常用到的魔法屬性和魔法方法。
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的系統(tǒng)文件都在使用這個特性對功能進行描述诽里。
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 程序開發(fā)過程中經常會導入很多其它模塊或者創(chuàng)建很多類的實例對象焰宣。
并且,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__ 方法用來對實例中的屬性進行初始化操作帆调,在使用類創(chuàng)建實例對象時,就會自動調用這方法豆同。
但是 __init__ 方法并不是在創(chuàng)建對象時第一個被執(zhí)行的方法番刊。
在創(chuàng)建對象時,Pyhton 會先在內存中申請對象的內存空間诱告。如果申請成功撵枢,說明對象才創(chuàng)建成功民晒,之后才是使用 __init__ 進行初始化操作。
而申請對象空間這個過程就是由 __new__ 方法來執(zhí)行的锄禽。
也就是說在創(chuàng)建對象過程中潜必,會先執(zhí)行 __new__ 在內存中申請實例存儲空間,然后再執(zhí)行 __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__ 方法來初始化對象空間。
開發(fā)過程中宵晚,一般不會重寫 __new__ 方法育苟。一般都是重寫 __init__ 方法。
Python官方文檔的說法派任,__new__ 方法主要是當你繼承一些不可變的class時(比如int, str, tuple)赶诊, 提供給你一個自定義這些類的實例化過程的途徑。 比如逸贾,要繼承 int 類陨仅,實現(xiàn)一個永遠是整數的類。 另外铝侵,在實現(xiàn)單例類的時候可以使用 __new__ 灼伤。
1.6.5 __call__ 方法
需求:記錄一個函數執(zhí)行的次數
在程序設計過程中,并不建議使用全局變量咪鲜,因為會大量占用內存空間不釋放狐赡,會破壞程序的封裝性。 而且全局變量在使用過程中疟丙,任何人都可以訪問颖侄,不安全,不符合面向對象的封裝思想隆敢。
這樣就可以使用 __call__ 方法來實現(xiàn)這個需求
實現(xiàn)__call__后发皿,可以將對象當做函數一樣去使用,稱為仿函數或函數對象
那么普通函數和函數對象有什么區(qū)別拂蝎?
普通對象在執(zhí)行完成后就結束了穴墅,不能保存函數執(zhí)行的狀態(tài)。而且在擴展其它函數時温自,函數間的關聯(lián)性不強玄货。
函數對象可以保存函數的狀態(tài)。比如要實現(xiàn)對象調用的次數悼泌。而且可以在類中擴展更多的功能松捉。
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__()攔截复斥,從而進行相應的操作,如賦值械媒,修改內容目锭,刪除內容等等。
如果現(xiàn)在要設計一個學生管理的類纷捞,實現(xiàn)學生信息的添加痢虹,獲取,刪除等操作主儡,應該怎么設計奖唯?
但是我們發(fā)現(xiàn),這個管理系統(tǒng)在管理學生時缀辩,實際使用的是一個字典臭埋,在管理學生信息的時候,能否不使用類提供的函數臀玄,而像使用字典一樣,直接通過操作字典的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)
# 創(chuàng)建學生管理對象
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="">
那么自定義類,能不能像系統(tǒng)列表一樣脆贵,顯示所有的信息呢医清?
這個功能可以通過 __str__ 方法來實現(xiàn)
...
? ? def __str__(self):
? ? ? ? return '[' + " : ".join(self.__students.values()) + ']'
程序運行結果 :
[tom : jack]
小結:
__str__ 的作用是一個自定義的類型,在轉換成字符串類型時卖氨,程序是不知道怎么辦的的会烙,因為程序也不知道你倒底寫了什么负懦。
python提供 __str__ 這個方法,讓開發(fā)人員重寫這個方法柏腻,制定自定義對象轉換成字符串的規(guī)則密似。
1.6.8 總結
? 魔法屬性和魔法方法都是 Python 定義好的一些屬性或方法,用來實現(xiàn)一些特定的功能。
? 魔法屬性和魔法方法的主要作用是用來簡化 Python 的操作葫盼,在編寫代碼時讓使用方式更加簡潔残腌。
1.7 多重繼承和多繼承
學習目標
? 1. 能夠說出什么是繼承
? 2. 能夠說出繼承的作用
? 3. 能夠說出多重繼承的初始化過程
? 4. 能夠說出多繼承的初始化過程
? 5. 什么是鉆石繼承
--------------------------------------------------------------------------------
1.7.1 繼承概述
在面向對象程序開發(fā)中,繼承是一個非常重要的概念贫导。也是一個非常貼近現(xiàn)實繼承關系的一種思想抛猫。
通過繼承,可以讓子類去擁有父類的屬性和方法孩灯,而不必重新編寫相同的代碼闺金,并且可以在父類的基礎上添加新的屬性和功能。
在繼承的同時峰档,子類還可以重寫父類中的方法败匹,從而獲取與父類不同的功能,實現(xiàn)多態(tài)讥巡。
在 Python 中 所有的類都是存在繼承關系的掀亩,沒有顯示繼承的,都是默認繼承 object 類
1.7.2 繼承的作用
? 子類在繼承父類的同時可以向子類中添加新的屬性和功能欢顷,提高開發(fā)效率槽棍,避免代碼冗余。
? 實現(xiàn)多態(tài)抬驴。
1.7.3 繼承回顧
單重單繼承
通過繼承炼七,讓子類調用方法,如果子類存在就直接調用執(zhí)行布持,如果沒有豌拙,就會到父類中去查找,如果父類中有就執(zhí)行父類中的方法题暖,如果沒有就再去object 類中去查找按傅,如果父類中沒有就會報錯。
1.7.4 多重單繼承的初始化問題
在設計單類時芙委,初始化數據時逞敷,只需要重寫__init__方法即可,可是當有多個類發(fā)生關系時灌侣,類中的屬性該何進行初始化呢推捐?
在發(fā)生繼承時,子類會繼承父類的屬性的方法侧啼。
當在初始化時牛柒,子類只需要初始化自己擴展的屬性即可堪簿,父類中的屬性交給父類去初始化。
使用 父類名.__init__()的形式來調用父類的初始化方法
為什么要這么做呢皮壁?
? 父類中的屬性是私有的椭更,子類根本不知道父類里的屬性是什么。
? 子類也不知道父類在初始化操作過程中做了哪些工作蛾魄。
? 所以誰的內容就交給誰去執(zhí)行是最安全的虑瀑。
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())
程序執(zhí)行結果:
Peron init...
Father init...
Son init...
Tom 18 男
1.7.5 多重多繼承的初始化問題
多重多繼承時使用 父類名.__init__()的形式來調用父類的初始化方法時,因為 Father 類和 Mother 類都是繼承于 Person 類的滴须,在自己的初始化方法中舌狗,都執(zhí)行了父類的初始化方法,所以Person 類的 __init__ 方法被執(zhí)行了兩次扔水。
? 多重多繼承的初始化使用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())
程序執(zhí)行結果:
Peron init...
Father init...
Mother init...
Son init...
Tom 18 男 老師
通過執(zhí)行結果來看魔市,使用 super 改進后的代碼只初始化了一次 Person 的初始化方法主届。
這種初始化方式稱為鉆石繼承(菱形繼承),如圖顯示 :
當程序使用了 super 函數以后待德,可以正常初始化君丁,是以__mro__方法解析順序為依據。
1.7.6 __mro__
mro Method Resolution Order 方法解析順序
類名.__mro__返回一個元組類型的繼承鏈磅网,來確定繼承的順序
在類出現(xiàn)繼承時谈截,每個類中都會保存一個當前類的繼承關系的表。
super 函數在執(zhí)行時涧偷,會在自己保存的這個繼承關系中去查找第一個參數,也就是當前的類名的下一個類是誰毙死。然后去調用下個類的初始化方法燎潮。
Python 中使用廣度優(yōu)先算法來決定繼承關系的排序。(廣度優(yōu)先算法可自行查找資料了解)
廣度優(yōu)先算法:橫向優(yōu)先
深度優(yōu)先算法:縱向優(yōu)先
MRO順序
print(Son.__mro__)
程序執(zhí)行結果 :
(<class '__main__.Son'>, <class '__main__.Mother'>, <class '__main__.Father'>, <class '__main__.Person'>, <class 'object'>)
1.7.7 super的簡化寫法
在初始化父類時扼倘,也可以使用 super().__init__() 函數來調用确封,簡化super函數的寫法。
這時再菊,super函數中沒有參數爪喘,還是能正確執(zhí)行,就是依照 __mro__ 中的順序來執(zhí)行的纠拔。
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())
程序執(zhí)行結果:
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__)
程序執(zhí)行結果:
(<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__ 來確定繼承的順序略水,使用的是廣度優(yōu)先算法(橫向優(yōu)先)