Python 中類的構(gòu)造方法 __new__ 的妙用
Python 的類中,所有以雙下劃線__包起來的方法,叫魔術(shù)方法,魔術(shù)方法在類或?qū)ο蟮哪承┦录l(fā)出后可以自動(dòng)執(zhí)行,讓類具有神奇的魔力,比如常見的構(gòu)造方法__new__、初始化方法__init__、析構(gòu)方法__del__,今天來聊一聊__new__的妙用,主要分享以下幾點(diǎn):
__new__和__init__的區(qū)別應(yīng)用1:改變內(nèi)置的不可變類型 應(yīng)用2:實(shí)現(xiàn)一個(gè)單例 應(yīng)用3:客戶端緩存 應(yīng)用4:不同文件不同的解密方法 應(yīng)用5:Metaclasses
__new__ 和 __init__ 的區(qū)別
1、調(diào)用時(shí)機(jī)不同:new 是真正創(chuàng)建實(shí)例的方法,init 用于實(shí)例的初始化,new 先于 init 運(yùn)行。
2、返回值不同,new 返回一個(gè)類的實(shí)例,而 init 不返回任何信息。
3、new 是 class 的方法,而 init 是對(duì)象的方法。
示例代碼:
class?A:
????def?__new__(cls,?*args,?**kwargs):
????????print("new",?cls,?args,?kwargs)
????????return?super().__new__(cls)
????def?__init__(self,?*args,?**kwargs):
????????print("init",?self,?args,?kwargs)
def?how_object_construction_works():
????x?=?A(1,?2,?3,?x=4)
????print(x)????
????print("===================")
????x?=?A.__new__(A,?1,?2,?3,?x=4)
????if?isinstance(x,?A):
????????type(x).__init__(x,?1,?2,?3,?x=4)
????print(x)
if?__name__?==?"__main__":
????how_object_construction_works()
上述代碼定義了一個(gè)類 A,在調(diào)用 A(1, 2, 3, x=4) 時(shí)先執(zhí)行 new,再執(zhí)行 init,等價(jià)于:
x?=?A.__new__(A,?1,?2,?3,?x=4)
if?isinstance(x,?A):
????type(x).__init__(x,?1,?2,?3,?x=4)
代碼的運(yùn)行結(jié)果如下:
new?'__main__.A'>?(1,?2,?3)?{'x':?4}
init?<__main__.A?object?at?0x7fccaec97610>?(1,?2,?3)?{'x':?4}
<__main__.A?object?at?0x7fccaec97610>
===================
new?'__main__.A'>?(1,?2,?3)?{'x':?4}
init?<__main__.A?object?at?0x7fccaec97310>?(1,?2,?3)?{'x':?4}
<__main__.A?object?at?0x7fccaec97310>
new 的主要作用就是讓程序員可以自定義類的創(chuàng)建行為,以下是其主要應(yīng)用場(chǎng)景:
應(yīng)用1:改變內(nèi)置的不可變類型
我們知道,元組是不可變類型,但是我們繼承 tuple ,然后可以在 new 中,對(duì)其元組的元素進(jìn)行修改,因?yàn)?new 返回之前,元組還不是元組,這在 init 函數(shù)中是無法實(shí)現(xiàn)的。比如說,實(shí)現(xiàn)一個(gè)大寫的元組,代碼如下:
class?UppercaseTuple(tuple):
????def?__new__(cls,?iterable):
????????upper_iterable?=?(s.upper()?for?s?in?iterable)
????????return?super().__new__(cls,?upper_iterable)
????#?以下代碼會(huì)報(bào)錯(cuò),初始化時(shí)是無法修改的
????#?def?__init__(self,?iterable):
????#?????print(f'init?{iterable}')
????#?????for?i,?arg?in?enumerate(iterable):
????#?????????self[i]?=?arg.upper()
if?__name__?==?'__main__':
????print("UPPERCASE?TUPLE?EXAMPLE")
????print(UppercaseTuple(["hello",?"world"]))
#?UPPERCASE?TUPLE?EXAMPLE
#?('HELLO',?'WORLD')
應(yīng)用2:實(shí)現(xiàn)一個(gè)單例
class?Singleton:
????_instance?=?None
????def?__new__(cls,?*args,?**kwargs):
????????if?cls._instance?is?None:
????????????cls._instance?=?super().__new__(cls,?*args,?**kwargs)
????????return?cls._instance
if?__name__?==?"__main__":
????print("SINGLETON?EXAMPLE")
????x?=?Singleton()
????y?=?Singleton()
????print(f"{x?is?y=}")
#?SINGLETON?EXAMPLE
#?x?is?y=True
應(yīng)用3:客戶端緩存
當(dāng)客戶端的創(chuàng)建成本比較高時(shí),比如讀取文件或者數(shù)據(jù)庫(kù),可以采用以下方法,同一個(gè)客戶端屬于同一個(gè)實(shí)例,節(jié)省創(chuàng)建對(duì)象的成本,這本質(zhì)就是多例模式。
class?Client:
????_loaded?=?{}
????_db_file?=?"file.db"
????def?__new__(cls,?client_id):
????????if?(client?:=?cls._loaded.get(client_id))?is?not?None:
????????????print(f"returning?existing?client?{client_id}?from?cache")
????????????return?client
????????client?=?super().__new__(cls)
????????cls._loaded[client_id]?=?client
????????client._init_from_file(client_id,?cls._db_file)
????????return?client
????def?_init_from_file(self,?client_id,?file):
????????#?lookup?client?in?file?and?read?properties
????????print(f"reading?client?{client_id}?data?from?file,?db,?etc.")
????????name?=?...
????????email?=?...
????????self.name?=?name
????????self.email?=?email
????????self.id?=?client_id
if?__name__?==?'__main__':
????print("CLIENT?CACHE?EXAMPLE")
????x?=?Client(0)
????y?=?Client(0)
????print(f"{x?is?y=}")
????z?=?Client(1)
#?CLIENT?CACHE?EXAMPLE
#?reading?client?0?data?from?file,?db,?etc.
#?returning?existing?client?0?from?cache
#?x?is?y=True
#?reading?client?1?data?from?file,?db,?etc.
應(yīng)用4:不同文件不同的解密方法
先在腳本所在目錄創(chuàng)建三個(gè)文件:plaintext_hello.txt、rot13_hello.txt、otp_hello.txt,程序會(huì)根據(jù)不同的文件選擇不同的解密算法
import?codecs
import?itertools
class?EncryptedFile:
????_registry?=?{}??#?'rot13'?->?ROT13Text
????def?__init_subclass__(cls,?prefix,?**kwargs):
????????super().__init_subclass__(**kwargs)
????????cls._registry[prefix]?=?cls
????def?__new__(cls,?path:?str,?key=None):
????????prefix,?sep,?suffix?=?path.partition(":///")
????????if?sep:
????????????file?=?suffix
????????else:
????????????file?=?prefix
????????????prefix?=?"file"
????????subclass?=?cls._registry[prefix]
????????obj?=?object.__new__(subclass)
????????obj.file?=?file
????????obj.key?=?key
????????return?obj
????def?read(self)?->?str:
????????raise?NotImplementedError
class?Plaintext(EncryptedFile,?prefix="file"):
????def?read(self):
????????with?open(self.file,?"r")?as?f:
????????????return?f.read()
class?ROT13Text(EncryptedFile,?prefix="rot13"):
????def?read(self):
????????with?open(self.file,?"r")?as?f:
????????????text?=?f.read()
????????return?codecs.decode(text,?"rot_13")
class?OneTimePadXorText(EncryptedFile,?prefix="otp"):
????def?__init__(self,?path,?key):
????????if?isinstance(self.key,?str):
????????????self.key?=?self.key.encode()
????def?xor_bytes_with_key(self,?b:?bytes)?->?bytes:
????????return?bytes(b1?^?b2?for?b1,?b2?in?zip(b,?itertools.cycle(self.key)))
????def?read(self):
????????with?open(self.file,?"rb")?as?f:
????????????btext?=?f.read()
????????text?=?self.xor_bytes_with_key(btext).decode()
????????return?text
if?__name__?==?"__main__":
????print("ENCRYPTED?FILE?EXAMPLE")
????print(EncryptedFile("plaintext_hello.txt").read())
????print(EncryptedFile("rot13:///rot13_hello.txt").read())
????print(EncryptedFile("otp:///otp_hello.txt",?key="1234").read())
#?ENCRYPTED?FILE?EXAMPLE
#?plaintext_hello.txt
#?ebg13_uryyb.gkg
#?^FCkYW_X^GLE
應(yīng)用5:Metaclasses
metaclass 可以像裝飾器那樣定制和修改繼承它的子類,前文Python黑魔法之metaclass
最后
本文分享了 Python 的構(gòu)造方法 __new__ 的常用場(chǎng)景,如果有幫助,請(qǐng)點(diǎn)個(gè)在看分享給在學(xué)習(xí) Python 的朋友們,點(diǎn)個(gè)贊也行,感謝你的支持。
關(guān)注我,每天學(xué)習(xí)一個(gè) Python 小技術(shù)。
