溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python中__new__方法有什么用

發(fā)布時間:2022-03-31 12:33:11 來源:億速云 閱讀:172 作者:小新 欄目:開發(fā)技術

這篇文章主要為大家展示了“Python中__new__方法有什么用”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“Python中__new__方法有什么用”這篇文章吧。

一、__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)
# 可以使用內置函數(shù)id()查看python對象的內存地址
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__()方法初始化一個類的實例,當代碼中實例化一個類的時候,第一個調用執(zhí)行的是__new__()方法,當定義的類中沒有重新定義__new__()方法時候,Python會默認調用該父類的__new__()方法來構造該實例,new方法就是先創(chuàng)建一個空間,然后每次創(chuàng)建一個實例化的對象,然后用開辟的空間存放這個實例化對象; 再次創(chuàng)建一個實例化的對象的時候,再用new方法開辟一個空間存放實例化對象。注意只有繼承了object的類才有此方法。

2、內存地址和對象可相互轉換

#通過_ctypes的api進行對內存地址的對象
import _ctypes
obj = _ctypes.PyObj_FromPtr(id(a))
#打印出來通過內存地址尋找到的對象
print(obj)

print(id(a))與 print(id(b))打印出來的都是內存地址(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)建實例,類實例化之前會首先調用,它是class的方法,是個靜態(tài)方法。而__init__()方法用戶初始化實例,該方法用在實例對象創(chuàng)建后被調用,它是實例對象的方法,用于設置類實例對象的一些初始值。

如果類中同時出現(xiàn)了__init__()方法和__new__()方法,則先調用__new__()方法后調用__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)都是在調用父類的new方法,必須把父類的new方法返回給函數(shù),才能開辟空間,因此必須添加return。代碼的執(zhí)行順序是:先執(zhí)行new方法,然后執(zhí)行init方法,最后是其它方法。

二、單例模式

單例模式最初的定義出現(xiàn)于《設計模式》:“保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。”

單例的使用主要是在需要保證全局只有一個實例可以被訪問的情況,比如系統(tǒng)日志的輸出、操作系統(tǒng)的任務管理器等。

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)建,我們應該調用父類的方法,為第一個對象分配空間
        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))
# 內存地址,而且它們的內存地址都是一樣的
print(a.name)
print(b.name)

輸出

初始化init A11 10
解析完成finish A11
初始化init A12 10
解析完成finish A12
2465140199816
2465140199816
A12
A12 

注:

1、單例模式始終只有一個空間,該空間一直重復利用。

首先定義一個類的私有屬性_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)了單例模式對象空間的重復利用,但是有時候我們想初始化過程只加載一次,避免頻繁請求浪費系統(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
        #使用類名調用類變量,不能直接訪問。
        if Solution.init_flag:
            return
        self.xml_load(self.data)
        # 修改類屬性的標記
        Solution.init_flag = True

    def __new__(cls, *args, **kwargs):
        # 判斷該類的屬性是否為空;對第一個對象沒有被創(chuàng)建,我們應該調用父類的方法,為第一個對象分配空間
        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)加載一次初始化過程。從以上兩次實例化過程結果來看,對象引用地址不變,結果被最后一次實例化數(shù)據(jù)覆蓋且初始化init只被打印一次。

2、單例模式下一次資源加載注意點

單例模式下控制類僅進行一次初始化過程適用于資源一次性加載進緩存的過程,對于多進程應用可采用多例模式實現(xiàn)。

三、多例模式

多個實例對象空間引用地址完全獨立,從而保持避免不同請求資源不被占用。將同一個對象請求歸為同一個實例。

class Solution:
    ##定義類實例化對象字典,即不同的實例對象對應不同的對象空間地址引用
    _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:
    ##定義類實例化對象字典,即不同的實例對象對應不同的對象空間地址引用
    _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)建的實例對象。從測試結果來看,創(chuàng)建相同的實例對象name="A11"時,第二次將略過初始化數(shù)據(jù)加載過程,很好的實現(xiàn)了緩存機制。

以上是“Python中__new__方法有什么用”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI