Python中類別的建構方法__new__方法有何作用?
Python類別中有些方法名稱、屬性名稱的前後都會加上__雙下畫線,這種方法、屬性通常屬於Python的特殊方法和特殊屬性。透過重寫這些方法或直接呼叫這些方法來實現特殊功能。今天來聊聊建構方法__new__實際程式的應用場景。
我們知道常見的初始化__init__方法,可以重寫實作自己想要的初始化邏輯。最近實際業務開發過程中碰到一類問題例如資料資源載入快取機制的實現,用到了魔法方法中建構方法,其中__init__()和__new__是物件的建構器,合理運用將有效提高程式效能。
希望大家多結合自己的業務需求深刻理解,靈活運用,讓程式碼變得更優雅。
接下來透過實例逐步詳細闡述__ new __ 方法在類別初始化過程中是什麼樣的存在!
class Solution(object): def __init__(self, name=None,data=None): 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.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) # 可以使用内置函数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__()方法初始化一個類別的實例,當程式碼中實例化一個類別的時候,第一個呼叫執行的是__new__()方法,當定義的類別中沒有重新定義__new__()方法時候,Python會預設呼叫該父類別的__new__()方法來建構這個實例,new方法就是先創建一個空間,然後每次創建一個實例化的對象,然後用開闢的空間存放這個實例化對象; 再次創建一個實例化的對象的時候,再用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)傳回了類別的名稱和物件的位址,但是兩者並不相同。每次實例化類別都會建立分配不同的物件位址,因此,程式碼實例化類別過程中傳回類別物件的位址參考也就不同。
class Solution: """ 注:new方法是为实例化对象创建空间的方法,现在new方法被改写,没有将实例化对象引用返回给python的解释器 无法为实例化对象创建空间存储,所以运行代码会报错。也没有完成初始化操作。 """ def __new__(cls, *args, **kwargs): print("对象创建空间") 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__()方法區別
__new__()方法用來建立實例,類別實例化之前會先調用,它是class的方法,是靜態方法。而__init__()方法使用者初始化實例,該方法用在實例物件建立後被調用,它是實例物件的方法,用於設定類別實例物件的一些初始值。
如果類別中同時出現了__init__()方法和__new__()方法,則先呼叫__new__()方法後呼叫__init__()方法。 __new__()方法是建立實例的第一步,執行完了需要傳回已建立的類別的實例,否則則報錯,無法執行__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方法返回給函數,才能開闢空間,因此必須加入return。程式碼的執行順序是:先執行new方法,然後執行init方法,最後是其它方法。
單例模式最初的定義出現於《設計模式》:“保證一個類別僅有一個實例,並提供一個訪問它的全局訪問點。”
單例的使用主要是在需要保證全域只有一個實例可以被存取的情況,例如係統日誌的輸出、作業系統的任務管理器等。
class Solution: # 1、记录第一个被创建对象的引用,代表着类的私有属性 _instance = None # 静态变量 存储在类的命名空间里的 def __init__(self,name,data): self.name = name self.data = data self.xml_load(self.data) def __new__(cls, *args, **kwargs): # 2.判断该类的属性是否为空;对第一个对象没有被创建,我们应该调用父类的方法,为第一个对象分配空间 if cls._instance == None: # 3.把类属性中保存的对象引用返回给python的解释器 cls._instance = object.__new__(cls)# 3 return cls._instance # 如果cls._instance不为None,直接返回已经实例化了的实例对象 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)#第一次开辟一个对象空间地址,后面创建都是在该地址上进行的 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,用來記錄第一個被建立物件的引用,如果cls._instance為None說明該類別還沒有實例化過,則實例化該類別並傳回實例物件。
透過以下資料測試可知,print(obj.name, obj.data)最後列印出來的都是A12,第一次列印"A11"時,屬性為空,執行if語句開啟了一個空間存放該屬性;從第二次打已經開闢了空間,執行else語句,直接返回"A12"到原來的空間中,把前面的蓋數據覆蓋掉。
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)、單例模式另外一種實作方法
def __new__(cls,*args,**kwargs): # hasattr查询目标并判断有没有,not1==1返回的是False # if语句后面的 # not 条件整体为True时,执行cls.instance = object....代码 # if语句后面的 # not 条件整体为False时,执行return代码 if not hasattr(cls,"instance"): # hasattr查、判断的作用 cls.instance = object.__new__(cls) return cls.instance
以上實現了單例模式物件空間的重複利用,但是有時候我們想初始化過程只載入一次,避免頻繁請求浪費系統資源(如資料庫連線請求資料)。
class Solution: #定义类变量 # 记录第一个被创建对象的引用,代表着类的私有属性 _instance = None #记录是否执行过初始化动作 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): # 判断该类的属性是否为空;对第一个对象没有被创建,我们应该调用父类的方法,为第一个对象分配空间 if cls._instance == None: # 把类属性中保存的对象引用返回给python的解释器 cls._instance = object.__new__(cls) return cls._instance #如果cls._instance不为None,直接返回已经实例化了的实例对象 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)#第一次实例化对象地址,后面创建都是在该地址上进行的 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属性来记录是否已经执行过初始化操作即可实现加载一次初始化过程。从以上两次实例化过程结果来看,对象引用地址不变,结果被最后一次实例化数据覆盖且初始化init只被打印一次。
2)、单例模式下一次资源加载注意点
单例模式下控制类仅进行一次初始化过程适用于资源一次性加载进缓存的过程,对于多进程应用可采用多例模式实现。
多个实例对象空间引用地址完全独立,从而保持避免不同请求资源不被占用。将同一个对象请求归为同一个实例。
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"已经存在访问对象 {name}") print(client) return client # 把类属性中保存的对象引用返回给python的解释器 print(f"正在创建访问对象 {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)、多例模式始终具有多个空间,不同空间完全独立。
我们在类空间中定义类实例化对象字典,即建立不同的实例对象和对象空间地址引用键值对,从而实现多例模式。通过类字典判断实例对象是否创建,节省创建的成本。
2)、多例模式测试过程
当创建相同的实例对象name="A11"时,程序首先在实例池中搜索cls._loaded.get(name),若存在则直接返回已创建的实例对象空间。多例模式完美的实现了不同访问对象具体不同的实例化对象地址。
多例模式实例 正在创建访问对象 A11 <__main__.Solution object at 0x000001C105AA5EC8> 初始化init A11 10 解析完成finish A11 已经存在访问对象 A11 <__main__.Solution object at 0x000001C105AA5EC8> 初始化init A11 10 解析完成finish A11 正在创建访问对象 A12 <__main__.Solution object at 0x000001C105AA5F88> 初始化init A12 20 解析完成finish A12 True A11 A11 A12
3)、多例模式下缓冲机制的实现
进一步优化多例模式初始化过程,比如读取文件或者数据库时仅进行一次初始化加载。
class Solution: ##定义类实例化对象字典,即不同的实例对象对应不同的对象空间地址引用 _loaded = {} def __new__(cls, name,data,*args): if cls._loaded.get(name) is not None: client = cls._loaded.get(name) print(f"已经存在访问对象 {name}") print(client) return client print(f"正在创建访问对象 {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)
多例模式实例 正在创建访问对象 A11 <__main__.Solution object at 0x0000024198989148> 初始化init A11 10 解析完成finish A11 已经存在访问对象 A11 <__main__.Solution object at 0x0000024198989148> 解析完成finish A11 正在创建访问对象 A12 <__main__.Solution object at 0x00000241989891C8> 初始化init A12 20 解析完成finish A12 True A11 A11 A12
注:多例模式下多个实例化对象均只进行一次初始化过程。
重写__new__方法中每个实例对象创建后绑定初始化_init_db()方法执行一次,后面遇到同一个实例对象将不会发生什么,直接返回已创建的实例对象。从测试结果来看,创建相同的实例对象name="A11"时,第二次将略过初始化数据加载过程,很好的实现了缓存机制。
本文结合项目背景详细介绍了__new__方法实现单例模式和多例模式以及缓存机制的实现!
1、__new__ 方法是在类创建实例的时候自动调用的。
2、 实例是通过类里面的 __ new __ 方法是在类创建出来的。
3、 先调用__new__ 方法创建实例,再调用 __ init __方法初始化实例。
4、 __new__ 方法,后面的括号里面的cls代表的是类本身。
5、__new__ 方法,判断类属性为空就去开辟空间,否则复用原来的地址。
更多的特殊方法比如1、自我描述方法:__repr__2、析构方法:__del__ 3、列出对象所有属性(包括方法)名:__dir__4、__dict__属性:查看对象内部所有属性名和属性值组成的字典5、__getattr____setattr__等。
当然还有metaclass类的__new__方法,可以动态修改程序中的一批类,这个功能在开发一些基础性的框架时非常有用,可以使用metaclass为某一批需要通用功能的类添加方法,若有合适实例场景后续分享。
以上是一文詳解Python中__new__方法的作用的詳細內容。更多資訊請關注PHP中文網其他相關文章!