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