Python中菱形继承的MRO顺序及property属性
Python中菱形繼承的MRO順序及property屬性
文章目錄
- Python中菱形繼承的MRO順序及property屬性
- 一、Python中菱形繼承的MRO順序
- 1. 單獨調(diào)用父類的方法
- 2. 多繼承中super調(diào)用有所父類的被重寫的方法
- 3. 單繼承中super
- 4.類名.__mro__
- 5.總結(jié)
- 6.例
- 二、類屬性和實例屬性
- 1. 類屬性、實例屬性
- 2. 實例方法、靜態(tài)方法和類方法
- 三、property屬性
- 1. 什么是property屬性
- 2. 簡單的實例
- 3. property屬性的有兩種方式
- 3.1 裝飾器方式
- 3.2 類屬性方式,創(chuàng)建值為property對象的類屬性
- 4、綜上所述:
- 四、with與“上下文管理器”
- 1、引入
- 2、什么是上下文(context)
- 3、上下文管理器
- 4、實現(xiàn)上下文管理器的另外方式
- 5、總結(jié)
一、Python中菱形繼承的MRO順序
1. 單獨調(diào)用父類的方法
# coding=utf-8print("******多繼承使用類名.__init__ 發(fā)生的狀態(tài)******") class Parent(object):def __init__(self, name):print('parent的init開始被調(diào)用')self.name = nameprint('parent的init結(jié)束被調(diào)用')class Son1(Parent):def __init__(self, name, age):print('Son1的init開始被調(diào)用')self.age = ageParent.__init__(self, name)print('Son1的init結(jié)束被調(diào)用')class Son2(Parent):def __init__(self, name, gender):print('Son2的init開始被調(diào)用')self.gender = genderParent.__init__(self, name)print('Son2的init結(jié)束被調(diào)用')class Grandson(Son1, Son2):def __init__(self, name, age, gender):print('Grandson的init開始被調(diào)用')Son1.__init__(self, name, age) # 單獨調(diào)用父類的初始化方法Son2.__init__(self, name, gender)print('Grandson的init結(jié)束被調(diào)用')gs = Grandson('grandson', 12, '男') print('姓名:', gs.name) print('年齡:', gs.age) print('性別:', gs.gender)print("******多繼承使用類名.__init__ 發(fā)生的狀態(tài)******\n\n") 運行結(jié)果:******多繼承使用類名.__init__ 發(fā)生的狀態(tài)****** Grandson的init開始被調(diào)用 Son1的init開始被調(diào)用 parent的init開始被調(diào)用 parent的init結(jié)束被調(diào)用 Son1的init結(jié)束被調(diào)用 Son2的init開始被調(diào)用 parent的init開始被調(diào)用 parent的init結(jié)束被調(diào)用 Son2的init結(jié)束被調(diào)用 Grandson的init結(jié)束被調(diào)用 姓名: grandson 年齡: 12 性別: 男 ******多繼承使用類名.__init__ 發(fā)生的狀態(tài)******2. 多繼承中super調(diào)用有所父類的被重寫的方法
print("******多繼承使用super().__init__ 發(fā)生的狀態(tài)******") class Parent(object):def __init__(self, name, *args, **kwargs): # 為避免多繼承報錯,使用不定長參數(shù),接受參數(shù)print('parent的init開始被調(diào)用')self.name = nameprint('parent的init結(jié)束被調(diào)用')class Son1(Parent):def __init__(self, name, age, *args, **kwargs): # 為避免多繼承報錯,使用不定長參數(shù),接受參數(shù)print('Son1的init開始被調(diào)用')self.age = agesuper().__init__(name, *args, **kwargs) # 為避免多繼承報錯,使用不定長參數(shù),接受參數(shù)print('Son1的init結(jié)束被調(diào)用')class Son2(Parent):def __init__(self, name, gender, *args, **kwargs): # 為避免多繼承報錯,使用不定長參數(shù),接受參數(shù)print('Son2的init開始被調(diào)用')self.gender = gendersuper().__init__(name, *args, **kwargs) # 為避免多繼承報錯,使用不定長參數(shù),接受參數(shù)print('Son2的init結(jié)束被調(diào)用')class Grandson(Son1, Son2):def __init__(self, name, age, gender):print('Grandson的init開始被調(diào)用')# 多繼承時,相對于使用類名.__init__方法,要把每個父類全部寫一遍# 而super只用一句話,執(zhí)行了全部父類的方法,這也是為何多繼承需要全部傳參的一個原因# super(Grandson, self).__init__(name, age, gender)super().__init__(name, age, gender)print('Grandson的init結(jié)束被調(diào)用')print(Grandson.__mro__)gs = Grandson('grandson', 12, '男') print('姓名:', gs.name) print('年齡:', gs.age) print('性別:', gs.gender) print("******多繼承使用super().__init__ 發(fā)生的狀態(tài)******\n\n") 運行結(jié)果:******多繼承使用super().__init__ 發(fā)生的狀態(tài)****** (<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>) Grandson的init開始被調(diào)用 Son1的init開始被調(diào)用 Son2的init開始被調(diào)用 parent的init開始被調(diào)用 parent的init結(jié)束被調(diào)用 Son2的init結(jié)束被調(diào)用 Son1的init結(jié)束被調(diào)用 Grandson的init結(jié)束被調(diào)用 姓名: grandson 年齡: 12 性別: 男 ******多繼承使用super().__init__ 發(fā)生的狀態(tài)****** 注意: 以上2個代碼執(zhí)行的結(jié)果不同 如果2個子類中都繼承了父類,當在子類中通過父類名調(diào)用時,parent被執(zhí)行了2次 如果2個子類中都繼承了父類,當在子類中通過super調(diào)用時,parent被執(zhí)行了1次3. 單繼承中super
print("******單繼承使用super().__init__ 發(fā)生的狀態(tài)******") class Parent(object):def __init__(self, name):print('parent的init開始被調(diào)用')self.name = nameprint('parent的init結(jié)束被調(diào)用')class Son1(Parent):def __init__(self, name, age):print('Son1的init開始被調(diào)用')self.age = agesuper().__init__(name) # 單繼承不能提供全部參數(shù)print('Son1的init結(jié)束被調(diào)用')class Grandson(Son1):def __init__(self, name, age, gender):print('Grandson的init開始被調(diào)用')super().__init__(name, age) # 單繼承不能提供全部參數(shù)print('Grandson的init結(jié)束被調(diào)用')gs = Grandson('grandson', 12, '男') print('姓名:', gs.name) print('年齡:', gs.age) #print('性別:', gs.gender) print("******單繼承使用super().__init__ 發(fā)生的狀態(tài)******\n\n")4.類名.mro
可以使用類名.__mro__的方式查看super在多繼承中init的調(diào)用順序,其結(jié)果是有C3算法決定的,在多繼承中保證公共基類只被執(zhí)行一次
5.總結(jié)
- super().__init__相對于類名.init,在單繼承上用法基本無差
- 但在多繼承上有區(qū)別,super方法能保證每個父類的方法只會執(zhí)行一次,而使用類名的方法會導(dǎo)致方法被執(zhí)行多次,具體看前面的輸出結(jié)果(super相當于一種解決菱形繼承問題的方法)
- 多繼承時,使用super方法,對父類的傳參數(shù),應(yīng)該是由于python中super的C3算法導(dǎo)致的原因,必須把參數(shù)全部傳遞,否則會報錯
- 單繼承時,使用super方法,則不能全部傳遞,只能傳父類方法所需的參數(shù),否則會報錯
- 多繼承時,相對于使用類名.__init__方法,要把每個父類全部寫一遍
- 而使用super方法,只需寫一句話便執(zhí)行了全部父類的方法,這也是為何多繼承需要全部傳參的一個原因
6.例
以下的代碼的輸出將是什么? 說出你的答案并解釋。
class Parent(object):x = 1class Child1(Parent):passclass Child2(Parent):passprint(Parent.x, Child1.x, Child2.x) Child1.x = 2 print(Parent.x, Child1.x, Child2.x) Parent.x = 3 print(Parent.x, Child1.x, Child2.x)答案, 以上代碼的輸出是: 1 1 1 1 2 1 3 2 3- 使你困惑或是驚奇的是關(guān)于最后一行的輸出是 3 2 3 而不是 3 2 1。為什么改變了 Parent.x 的值還會改變Child2.x 的值,但是同時 Child1.x 值卻沒有改變?
- 這個答案的關(guān)鍵是,在 Python 中,類變量在內(nèi)部是作為字典處理的。
- 如果一個變量的名字沒有在當前類的字典中發(fā)現(xiàn),將搜索祖先類(比如父類)直到被引用的變量名被找到(如果這個被引用的變量名既沒有在自己所在的類又沒有在祖先類中找到,會引發(fā)一個 AttributeError 異常 )。
- 因此,在父類中設(shè)置 x = 1 會使得類變量 x 在引用該類和其任何子類中的值為 1。這就是因為第一個 print 語句的輸出是 1 1 1。
- 隨后,如果任何它的子類重寫了該值(例如,我們執(zhí)行語句 Child1.x = 2),然后,該值僅僅在子類中被改變。這就是為什么第二個 print 語句的輸出是 1 2 1。
- 最后,如果該值在父類中被改變(例如,我們執(zhí)行語句 Parent.x = 3),這個改變會影響到任何未重寫該值的子類當中的值(在這個示例中被影響的子類是 Child2,)。這就是為什么第三個 print 輸出是 3 2 3。
- 這里也說明的python中的繼承和C++中的繼承的區(qū)別,C++ 中的繼承是完全把父類當中的所有都拷貝一份到子類當中,而python中的繼承相當于是引用指向的
二、類屬性和實例屬性
1. 類屬性、實例屬性
它們在定義和使用中有所區(qū)別,而最本質(zhì)的區(qū)別是內(nèi)存中保存的位置不同,
- 實例屬性屬于對象
- 類屬性屬于類
由上述代碼可以看出【實例屬性需要通過對象來訪問】【類屬性通過類訪問】,在使用上可以看出實例屬性和類屬性的歸屬是不同的。
其在內(nèi)容的存儲方式類似如下圖:
由上圖看出:
- 類屬性在內(nèi)存中只保存一份
- 實例屬性在每個對象中都要保存一份
應(yīng)用場景:
通過類創(chuàng)建實例對象時,如果每個對象需要具有相同名字的屬性,那么就使用類屬性,用一份既可
2. 實例方法、靜態(tài)方法和類方法
方法包括:實例方法、靜態(tài)方法和類方法,三種方法在內(nèi)存中都歸屬于類,區(qū)別在于調(diào)用方式不同。
- 實例方法:由對象調(diào)用;至少一個self參數(shù);執(zhí)行實例方法時,自動將調(diào)用該方法的對象賦值給self;
- 類方法:由類調(diào)用; 至少一個cls參數(shù);執(zhí)行類方法時,自動將調(diào)用該方法的類賦值給cls;
- 靜態(tài)方法:由類調(diào)用;無默認參數(shù);
對比
- 相同點:對于所有的方法而言,均屬于類,所以 在內(nèi)存中也只保存一份
- 不同點:方法調(diào)用者不同、調(diào)用方法時自動傳入的參數(shù)不同。
三、property屬性
1. 什么是property屬性
一種用起來像是使用的實例屬性一樣的特殊屬性,可以對應(yīng)于某個方法
# ############### 定義 ############### class Foo:def func(self):pass# 定義property屬性@propertydef prop(self):pass# ############### 調(diào)用 ############### foo_obj = Foo() foo_obj.func() # 調(diào)用實例方法 foo_obj.prop # 調(diào)用property屬性property屬性的定義和調(diào)用要注意一下幾點:
- 定義時,在實例方法的基礎(chǔ)上添加 @property 裝飾器;并且僅有一個self參數(shù) 調(diào)用時,無需括號
- 方法:foo_obj.func()
- property屬性:foo_obj.prop
2. 簡單的實例
對于京東商城中顯示電腦主機的列表頁面,每次請求不可能把數(shù)據(jù)庫中的所有內(nèi)容都顯示到頁面上
而是通過分頁的功能局部顯示,所以在向數(shù)據(jù)庫中請求數(shù)據(jù)時就要顯示的指定獲取從第m條到第n條的所有數(shù)據(jù) 這個分頁的功能包括:
根據(jù)用戶請求的當前頁和總數(shù)據(jù)條數(shù)計算出 m 和 n
根據(jù)m 和 n 去數(shù)據(jù)庫中請求數(shù)據(jù)
# ############### 定義 ############### class Pager:def __init__(self, current_page):# 用戶當前請求的頁碼(第一頁、第二頁...)self.current_page = current_page# 每頁默認顯示10條數(shù)據(jù)self.per_items = 10 @propertydef start(self):val = (self.current_page - 1) * self.per_itemsreturn val@propertydef end(self):val = self.current_page * self.per_itemsreturn val# ############### 調(diào)用 ############### p = Pager(1) p.start # 就是起始值,即:m p.end # 就是結(jié)束值,即:n從上述可見
Python的property屬性的功能是:property屬性內(nèi)部進行一系列的邏輯計算,最終將計算結(jié)果返回。
3. property屬性的有兩種方式
- 裝飾器 即:在方法上應(yīng)用裝飾器
- 類屬性 即:在類中定義值為property對象的類屬性
3.1 裝飾器方式
在類的實例方法上應(yīng)用@property裝飾器
Python中的類有經(jīng)典類和新式類,新式類的屬性比經(jīng)典類的屬性豐富。( 如果類繼object,那么該類是新式類 )
經(jīng)典類,具有一種@property裝飾器
# ############### 定義 ############### class Goods:@propertydef price(self):return "laowang" # ############### 調(diào)用 ############### obj = Goods() result = obj.price # 自動執(zhí)行 @property 修飾的 price 方法,并獲取方法的返回值 print(result)新式類,具有三種@property裝飾器
#coding=utf-8
注意
- 經(jīng)典類中的屬性只有一種訪問方式,其對應(yīng)被 @property 修飾的方法
- 新式類中的屬性有三種訪問方式,并分別對應(yīng)了三個被@property、@方法名.setter、@方法名.deleter修飾的方法
- 由于新式類中具有三種訪問方式,我們可以根據(jù)它們幾個屬性的訪問特點,分別將三個方法定義為對同一個屬性:獲取、修改、刪除
3.2 類屬性方式,創(chuàng)建值為property對象的類屬性
當使用類屬性的方式創(chuàng)建property屬性時,經(jīng)典類和新式類無區(qū)別
class Foo:def get_bar(self):return 'laowang'BAR = property(get_bar)obj = Foo() reuslt = obj.BAR # 自動調(diào)用get_bar方法,并獲取方法的返回值 print(reuslt)property方法中有個四個參數(shù)
- 第一個參數(shù)是方法名,調(diào)用 對象.屬性 時自動觸發(fā)執(zhí)行方法
- 第二個參數(shù)是方法名,調(diào)用 對象.屬性 = XXX 時自動觸發(fā)執(zhí)行方法
- 第三個參數(shù)是方法名,調(diào)用 del 對象.屬性 時自動觸發(fā)執(zhí)行方法
- 第四個參數(shù)是字符串,調(diào)用 對象.屬性.doc ,此參數(shù)是該屬性的描述信息
由于類屬性方式創(chuàng)建property屬性具有3種訪問方式,我們可以根據(jù)它們幾個屬性的訪問特點,分別將三個方法定義為對同一個屬性:獲取、修改、刪除
class Goods(object):def __init__(self):# 原價self.original_price = 100# 折扣self.discount = 0.8def get_price(self):# 實際價格 = 原價 * 折扣new_price = self.original_price * self.discountreturn new_pricedef set_price(self, value):self.original_price = valuedef del_price(self):del self.original_pricePRICE = property(get_price, set_price, del_price, '價格屬性描述...')obj = Goods() obj.PRICE # 獲取商品價格 obj.PRICE = 200 # 修改商品原價 del obj.PRICE # 刪除商品原價4、綜上所述:
- 定義property屬性共有兩種方式,分別是【裝飾器】和【類屬性】,而【裝飾器】方式針對經(jīng)典類和新式類又有所不同。
- 通過使用property屬性,能夠簡化調(diào)用者在獲取數(shù)據(jù)的流程
四、with與“上下文管理器”
1、引入
- 對于系統(tǒng)資源如文件、數(shù)據(jù)庫連接、socket而言,應(yīng)用程序打開這些資源并執(zhí)行完業(yè)務(wù)邏輯之后,必須做的一件事就是要關(guān)閉(斷開)該資源。
- 比如 Python 程序打開一個文件,往文件中寫內(nèi)容,寫完之后,就要關(guān)閉該文件,否則會出現(xiàn)什么情況呢?
- 極端情況下會出現(xiàn) “Too many open files” 的錯誤,因為系統(tǒng)允許你打開的最大文件數(shù)量是有限的。
- 同樣,對于數(shù)據(jù)庫,如果連接數(shù)過多而沒有及時關(guān)閉的話,就可能會出現(xiàn) “Can not connect to MySQL server Too many connections”,因為數(shù)據(jù)庫連接是一種非常昂貴的資源,不可能無限制的被創(chuàng)建。
來看看如何正確關(guān)閉一個文件。
普通版: def m1():f = open("output.txt", "w")f.write("python之禪")f.close()這樣寫有一個潛在的問題,如果在調(diào)用 write 的過程中,出現(xiàn)了異常進而導(dǎo)致后續(xù)代碼無法繼續(xù)執(zhí)行,close 方法無法被正常調(diào)用,因此資源就會一直被該程序占用者釋放。那么該如何改進代碼呢?
進階版: def m2():f = open("output.txt", "w")try:f.write("python之禪")except IOError:print("oops error")finally:f.close()- 改良版本的程序是對可能發(fā)生異常的代碼處進行 try 捕獲,使用 try/finally 語句
- 該語句表示如果在 try 代碼塊中程序出現(xiàn)了異常,后續(xù)代碼就不再執(zhí)行,而直接跳轉(zhuǎn)到 except 代碼塊。
- 而無論如何,finally 塊的代碼最終都會被執(zhí)行。因此,只要把 close 放在 finally 代碼中,文件就一定會關(guān)閉。
- 一種更加簡潔、優(yōu)雅的方式就是用 with 關(guān)鍵字。
- open 方法的返回值賦值給變量 f,當離開 with 代碼塊的時候,系統(tǒng)會自動調(diào)用 f.close() 方法,
- with 的作用和使用 try/finally 語句是一樣的。
2、什么是上下文(context)
上下文在不同的地方表示不同的含義,要感性理解。context其實說白了,和文章的上下文是一個意思,在通俗一點,我覺得叫環(huán)境更好。…
林沖大叫一聲“啊也!”…
問:這句話林沖的“啊也”表達了林沖怎樣的心里?
答:啊你媽個頭啊!
看,一篇文章,給你摘錄一段,沒前沒后,你讀不懂,因為有語境,就是語言環(huán)境存在,一段話說了什么,要通過上下文(文章的上下文)來推斷。
3、上下文管理器
任何實現(xiàn)了 __enter__() 和 __exit__() 方法的對象都可稱之為上下文管理器,上下文管理器對象可以使用 with 關(guān)鍵字。顯然,文件(file)對象也實現(xiàn)了上下文管理器。
那么文件對象是如何實現(xiàn)這兩個方法的呢?我們可以模擬實現(xiàn)一個自己的文件類,讓該類實現(xiàn) __enter__() 和 __exit__() 方法。
class File():def __init__(self, filename, mode):self.filename = filenameself.mode = modedef __enter__(self):print("entering")self.f = open(self.filename, self.mode)return self.fdef __exit__(self, *args):print("will exit")self.f.close()enter() 方法返回資源對象,這里就是你將要打開的那個文件對象,exit() 方法處理一些清除工作。
因為 File 類實現(xiàn)了上下文管理器,現(xiàn)在就可以使用 with 語句了。
with File('out.txt', 'w') as f:print("writing")f.write('hello, python')這樣,你就無需顯示地調(diào)用 close 方法了,由系統(tǒng)自動去調(diào)用,哪怕中間遇到異常 close 方法也會被調(diào)用。
4、實現(xiàn)上下文管理器的另外方式
Python 還提供了一個 contextmanager 的裝飾器,更進一步簡化了上下文管理器的實現(xiàn)方式。通過 yield 將函數(shù)分割成兩部分,yield 之前的語句在 __enter__ 方法中執(zhí)行,yield 之后的語句在 __exit__ 方法中執(zhí)行。緊跟在 yield 后面的值是函數(shù)的返回值。
from contextlib import contextmanager@contextmanager def my_open(path, mode):f = open(path, mode)yield ff.close() 調(diào)用with my_open('out.txt', 'w') as f:f.write("hello , the simplest context manager")5、總結(jié)
Python 提供了 with 語法用于簡化資源操作的后續(xù)清除操作,是 try/finally 的替代方法,實現(xiàn)原理建立在上下文管理器之上。此外,Python 還提供了一個 contextmanager 裝飾器,更進一步簡化上下管理器的實現(xiàn)方式。
總結(jié)
以上是生活随笔為你收集整理的Python中菱形继承的MRO顺序及property属性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python中的GIL和深浅拷贝
- 下一篇: STL中sort算法简析