詳解Python中__new__方法的作用
前言
Python中類的構(gòu)造方法__new__方法有何作用?
Python類中有些方法名、屬性名的前后都添加__雙下畫線,這種方法、屬性通常屬于Python的特殊方法和特殊屬性。通過重寫這些方法或直接調(diào)用這些方法來實現(xiàn)特殊功能。今天來聊聊構(gòu)造方法__new__實際程序的應(yīng)用場景。
我們知道常見的初始化__init__方法,可以重寫實現(xiàn)自己想要的初始化邏輯。最近實際業(yè)務(wù)開發(fā)過程中碰到一類問題比如數(shù)據(jù)資源加載緩存機制的實現(xiàn),用到了魔法方法中構(gòu)造方法,其中__init__()和__new__是對象的構(gòu)造器,合理運用將有效提高程序性能。
希望大家多結(jié)合自己的業(yè)務(wù)需求深刻理解,靈活運用,使得代碼變得更加優(yōu)雅。
一、__new__方法簡介
接下來通過實例逐步詳細闡述__ new __ 方法在類初始化過程中是什么樣的存在!
1、初始化數(shù)據(jù)加載+解析類實例
class?Solution(object): ????def?__init__(self,?name=None,data=None): ????????self.name?=?name ????????self.data?=?data ????????#初始化加載數(shù)據(jù) ????????self.xml_load(self.data) ????def?xml_load(self,data): ????????print("初始化init",data) ????def?Parser(self): ????????print("解析完成finish",self.name) a?=?Solution(name="A111",data=10) a.Parser() b?=?Solution(name="A112",data=20) b.Parser() #?print(a)與?print(b)返回了類的名稱和對象的地址 print(a) print(b) #?可以使用內(nèi)置函數(shù)id()查看python對象的內(nèi)存地址 print(id(a)) print(id(b)) 初始化init?10 解析完成finish?A111 初始化init?20 解析完成finish?A112 <__main__.Solution?object?at?0x0000024A3AF28D48> <__main__.Solution?object?at?0x0000024A3B055C48> 2517839809864 2517841042504
注:
1、代碼實例化類過程
一般使用__init__()方法初始化一個類的實例,當代碼中實例化一個類的時候,第一個調(diào)用執(zhí)行的是__new__()方法,當定義的類中沒有重新定義__new__()方法時候,Python會默認調(diào)用該父類的__new__()方法來構(gòu)造該實例,new方法就是先創(chuàng)建一個空間,然后每次創(chuàng)建一個實例化的對象,然后用開辟的空間存放這個實例化對象; 再次創(chuàng)建一個實例化的對象的時候,再用new方法開辟一個空間存放實例化對象。注意只有繼承了object的類才有此方法。
2、內(nèi)存地址和對象可相互轉(zhuǎn)換
#通過_ctypes的api進行對內(nèi)存地址的對象 import?_ctypes obj?=?_ctypes.PyObj_FromPtr(id(a)) #打印出來通過內(nèi)存地址尋找到的對象 print(obj)
print(id(a))與 print(id(b))打印出來的都是內(nèi)存地址(10進制),print(a)與 print(b)返回了類的名稱和對象的地址,但是兩者并不相同。每次實例化類都會創(chuàng)建分配不同的對象地址,因此,代碼實例化類過程中返回類對象的地址引用也就不同。
2、初始化數(shù)據(jù)加載重寫new方法+解析類實例
class?Solution: ????""" ????注:new方法是為實例化對象創(chuàng)建空間的方法,現(xiàn)在new方法被改寫,沒有將實例化對象引用返回給python的解釋器 ????無法為實例化對象創(chuàng)建空間存儲,所以運行代碼會報錯。也沒有完成初始化操作。 ????""" ????def?__new__(cls,?*args,?**kwargs): ????????print("對象創(chuàng)建空間") ????????cls.instance?=?super().__new__(cls) ????????print(cls.instance) ????????# return cls.instance ??#若未返回實例對象引用,實例化方法將報錯:AttributeError:?'NoneType' object has no attribute 'Parser' ????def?__init__(self,name,data): ????????self.name?=?name ????????self.data?=?data ????????self.xml_load(self.data) ????def?xml_load(self,data): ????????print("初始化init",?data) ????def?Parser(self): ????????print("解析完成finish",self.data) a?=?Solution("A111",10) a.Parser() print(id(a))
注:
1、__init__()方法和__new__()方法區(qū)別
__new__()方法用于創(chuàng)建實例,類實例化之前會首先調(diào)用,它是class的方法,是個靜態(tài)方法。而__init__()方法用戶初始化實例,該方法用在實例對象創(chuàng)建后被調(diào)用,它是實例對象的方法,用于設(shè)置類實例對象的一些初始值。
如果類中同時出現(xiàn)了__init__()方法和__new__()方法,則先調(diào)用__new__()方法后調(diào)用__init__()方法。__new__()方法是創(chuàng)建實例的第一步,執(zhí)行完了需要返回創(chuàng)建的類的實例,否則則報錯,無法執(zhí)行__init__()方法。其中,__init__()方法將不返回任何信息。
2、重寫__new__()方法
def?__new__(cls,?*args,?**kwargs): ????print(cls)??#?cls?代表的是Solution這個類本身<class'__?main?__.Solution'> ????cls.instance?=?super().__new__(cls)??#?object().__?new?__() ????print(cls.instance) ????return?cls.instance
super()與object.__new__(cls)都是在調(diào)用父類的new方法,必須把父類的new方法返回給函數(shù),才能開辟空間,因此必須添加return。代碼的執(zhí)行順序是:先執(zhí)行new方法,然后執(zhí)行init方法,最后是其它方法。
二、單例模式
單例模式最初的定義出現(xiàn)于《設(shè)計模式》:“保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。”
單例的使用主要是在需要保證全局只有一個實例可以被訪問的情況,比如系統(tǒng)日志的輸出、操作系統(tǒng)的任務(wù)管理器等。
1、用new方法如何實現(xiàn)單例模式
class?Solution: ????#?1、記錄第一個被創(chuàng)建對象的引用,代表著類的私有屬性 ????_instance?=?None?#?靜態(tài)變量?存儲在類的命名空間里的 ????def?__init__(self,name,data): ????????self.name?=?name ????????self.data?=?data ????????self.xml_load(self.data) ????def?__new__(cls,?*args,?**kwargs): ????????# 2.判斷該類的屬性是否為空;對第一個對象沒有被創(chuàng)建,我們應(yīng)該調(diào)用父類的方法,為第一個對象分配空間 ????????if?cls._instance?==?None:?? ????????????#?3.把類屬性中保存的對象引用返回給python的解釋器 ????????????cls._instance?=?object.__new__(cls)??#?3 ????????????return?cls._instance ????????#?如果cls._instance不為None,直接返回已經(jīng)實例化了的實例對象 ????????else: ????????????return?cls._instance??#?必須把地址返回給new方法,讓它有存儲空間 ????def?xml_load(self,data): ????????print("初始化init",self.name,data) ????def?Parser(self): ????????print("解析完成finish",self.name) a?=?Solution("A11",10)??#第一次開辟一個對象空間地址,后面創(chuàng)建都是在該地址上進行的 a.Parser() b?=?Solution("A12",20)??#b把a覆蓋掉 b.Parser() print(id(a)) print(id(b)) #?內(nèi)存地址,而且它們的內(nèi)存地址都是一樣的 print(a.name) print(b.name)
輸出
初始化init A11 10
解析完成finish A11
初始化init A12 10
解析完成finish A12
2465140199816
2465140199816
A12
A12
注:
1、單例模式始終只有一個空間,該空間一直重復(fù)利用。
首先定義一個類的私有屬性_instance,用來記錄第一個被創(chuàng)建對象的引用,如果cls._instance為None說明該類還沒有實例化過,則實例化該類并返回實例對象。
通過以下數(shù)據(jù)測試可知,print(obj.name, obj.data)最后打印出來的都是A12,第一次打印"A11"時,屬性為空,執(zhí)行if語句開辟了一個空間存放該屬性;從 第二次打已經(jīng)開辟了空間 ,執(zhí)行else語句,直接返回"A12"到原來的空間中,把前面的蓋數(shù)據(jù)覆蓋掉。
def?task(id,data): ????obj?=?Solution("{0}".format(id),?"{0}".format(data)) ????print(obj.name,?obj.data) import?threading ID=["A11","A12","A13","A14","A12"] DATA=[10,20,30,40,20] for?i?in?range(5): ????t?=?threading.Thread(target=task(ID[i],DATA[i]),?args=[i,?]) ????t.start()
輸出
<__main__.Solution object at 0x00000221B2129148>
初始化init A11 10
A11 10
初始化init A12 20
A12 20
初始化init A13 30
A13 30
初始化init A14 40
A14 40
初始化init A12 20
A12 20
2、單例模式另外一種實現(xiàn)方法
def?__new__(cls,*args,**kwargs): ????#?hasattr查詢目標并判斷有沒有,not??1==1??返回的是False ????#?if語句后面的 ????#?not?條件整體為True時,執(zhí)行cls.instance?=?object....代碼 ????#?if語句后面的 ????#?not?條件整體為False時,執(zhí)行return代碼 ????if?not?hasattr(cls,"instance"):?????#?hasattr查、判斷的作用 ????????cls.instance?=?object.__new__(cls) ????return?cls.instance
2、如何控制類僅執(zhí)行一次初始化方法
以上實現(xiàn)了單例模式對象空間的重復(fù)利用,但是有時候我們想初始化過程只加載一次,避免頻繁請求浪費系統(tǒng)資源(如數(shù)據(jù)庫連接請求數(shù)據(jù))。
class?Solution: ????#定義類變量 ????#?記錄第一個被創(chuàng)建對象的引用,代表著類的私有屬性 ????_instance?=?None ????#記錄是否執(zhí)行過初始化動作 ????init_flag?=?False ????def?__init__(self,name,data): ????????self.name?=?name ????????self.data?=?data ????????#使用類名調(diào)用類變量,不能直接訪問。 ????????if?Solution.init_flag: ????????????return ????????self.xml_load(self.data) ????????#?修改類屬性的標記 ????????Solution.init_flag?=?True ????def?__new__(cls,?*args,?**kwargs): ????????#?判斷該類的屬性是否為空;對第一個對象沒有被創(chuàng)建,我們應(yīng)該調(diào)用父類的方法,為第一個對象分配空間 ????????if?cls._instance?==?None:? ????????????#?把類屬性中保存的對象引用返回給python的解釋器 ????????????cls._instance?=?object.__new__(cls)?? ????????????return?cls._instance ????????#如果cls._instance不為None,直接返回已經(jīng)實例化了的實例對象 ????????else: ????????????return?cls._instance? ????def?xml_load(self,data): ????????print("初始化init",self.name,data) ????def?Parser(self): ????????print("解析完成finish",self.name) a?=?Solution("A11",10)??#第一次實例化對象地址,后面創(chuàng)建都是在該地址上進行的 a.Parser() b?=?Solution("A12",20)??#b把a覆蓋掉 b.Parser() print(id(a)) print(id(b)) print(a.name) print(b.name)
輸出
初始化init A11 10
解析完成finish A11
解析完成finish A12
2280855720328
2280855720328
A12
A12
注:
1、單例模式下僅加載一次初始化過程。
這時候我們在類空間中再添加一個init_flag屬性來記錄是否已經(jīng)執(zhí)行過初始化操作即可實現(xiàn)加載一次初始化過程。從以上兩次實例化過程結(jié)果來看,對象引用地址不變,結(jié)果被最后一次實例化數(shù)據(jù)覆蓋且初始化init只被打印一次。
2、單例模式下一次資源加載注意點
單例模式下控制類僅進行一次初始化過程適用于資源一次性加載進緩存的過程,對于多進程應(yīng)用可采用多例模式實現(xiàn)。
三、多例模式
多個實例對象空間引用地址完全獨立,從而保持避免不同請求資源不被占用。將同一個對象請求歸為同一個實例。
class?Solution: ????##定義類實例化對象字典,即不同的實例對象對應(yīng)不同的對象空間地址引用 ????_loaded?=?{} ????def?__init__(self,name,data): ????????self.name?=?name ????????self.data?=?data ????????self.xml_load(self.data) ????def?__new__(cls,?name,*args): ????????if?cls._loaded.get(name)?is?not?None: ????????????client?=?cls._loaded.get(name) ????????????print(f"已經(jīng)存在訪問對象?{name}") ????????????print(client) ????????????return?client ????????#?把類屬性中保存的對象引用返回給python的解釋器 ????????print(f"正在創(chuàng)建訪問對象?{name}") ????????client?=?super().__new__(cls) ????????#?為該類實例name添加一個空間對象地址引用 ????????print(client)???? ????????cls._loaded[name]?=?client ????????return?client ????def?xml_load(self,data): ????????print("初始化init",self.name,data) ????def?Parser(self): ????????print("解析完成finish",self.name) if?__name__?==?'__main__': ????print("多例模式實例") ????a?=?Solution("A11",10) ????a.Parser() ????b?=?Solution("A11",10) ????b.Parser() ????c?=?Solution("A12",?20) ????c.Parser() ????print(f"{a?is?b}") ????print(a.name) ????print(b.name) ????print(c.name)
注:
1、多例模式始終具有多個空間,不同空間完全獨立。
我們在類空間中定義類實例化對象字典,即建立不同的實例對象和對象空間地址引用鍵值對,從而實現(xiàn)多例模式。通過類字典判斷實例對象是否創(chuàng)建,節(jié)省創(chuàng)建的成本。
2、多例模式測試過程
當創(chuàng)建相同的實例對象name="A11"時,程序首先在實例池中搜索cls._loaded.get(name),若存在則直接返回已創(chuàng)建的實例對象空間。多例模式完美的實現(xiàn)了不同訪問對象具體不同的實例化對象地址。
3、多例模式下緩沖機制的實現(xiàn)
進一步優(yōu)化多例模式初始化過程,比如讀取文件或者數(shù)據(jù)庫時僅進行一次初始化加載。
class?Solution: ????##定義類實例化對象字典,即不同的實例對象對應(yīng)不同的對象空間地址引用 ????_loaded?=?{} ????def?__new__(cls,?name,data,*args): ????????if?cls._loaded.get(name)?is?not?None: ????????????client?=?cls._loaded.get(name) ????????????print(f"已經(jīng)存在訪問對象?{name}") ????????????print(client) ????????????return?client ????????print(f"正在創(chuàng)建訪問對象?{name}") ????????#?把類屬性中保存的對象引用返回給python的解釋器 ????????client?=?super().__new__(cls) ????????print(client) ????????#?為該類實例name添加一個空間對象地址引用 ????????cls._loaded[name]?=?client ????????client._init_db(name,data) ????????return?client ????def?_init_db(self,name,data): ????????self.name?=?name ????????self.data?=?data ????????self.xml_load(self.data) ????def?xml_load(self,data): ????????print("初始化init",self.name,data) ????def?Parser(self): ????????print("解析完成finish",self.name) if?__name__?==?'__main__': ????print("多例模式實例-緩存") ????a?=?Solution("A11",10) ????a.Parser() ????b?=?Solution("A11",10) ????b.Parser() ????c?=?Solution("A12",?20) ????c.Parser() ????print(f"{a?is?b}") ????print(a.name) ????print(b.name) ????print(c.name)
輸出
正在創(chuàng)建訪問對象 A11
<__main__.Solution object at 0x0000024198989148>
初始化init A11 10
解析完成finish A11
已經(jīng)存在訪問對象 A11
<__main__.Solution object at 0x0000024198989148>
解析完成finish A11
正在創(chuàng)建訪問對象 A12
<__main__.Solution object at 0x00000241989891C8>
初始化init A12 20
解析完成finish A12
True
A11
A11
A12
注:多例模式下多個實例化對象均只進行一次初始化過程。
重寫__new__方法中每個實例對象創(chuàng)建后綁定初始化_init_db()方法執(zhí)行一次,后面遇到同一個實例對象將不會發(fā)生什么,直接返回已創(chuàng)建的實例對象。從測試結(jié)果來看,創(chuàng)建相同的實例對象name="A11"時,第二次將略過初始化數(shù)據(jù)加載過程,很好的實現(xiàn)了緩存機制。
總結(jié)
本文結(jié)合項目背景詳細介紹了__new__方法實現(xiàn)單例模式和多例模式以及緩存機制的實現(xiàn)!
1、__new__ 方法是在類創(chuàng)建實例的時候自動調(diào)用的。
2、 實例是通過類里面的 __ new __ 方法是在類創(chuàng)建出來的。
3、 先調(diào)用__new__ 方法創(chuàng)建實例,再調(diào)用 __ init __方法初始化實例。
4、 __new__ 方法,后面的括號里面的cls代表的是類本身。
5、__new__ 方法,判斷類屬性為空就去開辟空間,否則復(fù)用原來的地址。
更多的特殊方法比如1、自我描述方法:__repr__2、析構(gòu)方法:__del__ 3、列出對象所有屬性(包括方法)名:__dir__4、__dict__屬性:查看對象內(nèi)部所有屬性名和屬性值組成的字典5、__getattr__\__setattr__等。
當然還有metaclass類的__new__方法,可以動態(tài)修改程序中的一批類,這個功能在開發(fā)一些基礎(chǔ)性的框架時非常有用,可以使用metaclass為某一批需要通用功能的類添加方法。
以上就是詳解Python中__new__方法的作用的詳細內(nèi)容,更多關(guān)于Python __new__的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python中在for循環(huán)中嵌套使用if和else語句的技巧
Python的語法糖非常強大,比如Python中在for循環(huán)中嵌套使用if和else語句的技巧便十分給力,下面我們就舉幾個例子來看詳細的用法:2016-06-06python將類似json的數(shù)據(jù)存儲到MySQL中的實例
今天小編就為大家分享一篇python將類似json的數(shù)據(jù)存儲到MySQL中的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07python xlsxwriter庫生成圖表的應(yīng)用示例
這篇文章主要介紹了python xlsxwriter庫生成圖表的應(yīng)用示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03利用Python實現(xiàn)網(wǎng)絡(luò)測試的腳本分享
這篇文章主要給大家介紹了關(guān)于利用Python實現(xiàn)網(wǎng)絡(luò)測試的方法,文中給出了詳細的示例代碼供大家參考學習,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-05-05關(guān)于Python中flask-httpauth庫用法詳解
這篇文章主要介紹了關(guān)于Python中flask-httpauth庫用法詳解,Flask-HTTPAuth是一個?Flask?擴展,它簡化了?HTTP?身份驗證與?Flask?路由的使用,需要的朋友可以參考下2023-04-04