Python 中類的構(gòu)造方法 __New__ 的妙用
Python 的類中,所有以雙下劃線__包起來(lái)的方法,叫魔術(shù)方法,魔術(shù)方法在類或?qū)ο蟮哪承┦录l(fā)出后可以自動(dòng)執(zhí)行,讓類具有神奇的魔力,比如常見(jiàn)的構(gòu)造方法__new__、初始化方法__init__、析構(gòu)方法__del__,今天來(lái)聊一聊__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 <class '__main__.A'> (1, 2, 3) {'x': 4}
- init <__main__.A object at 0x7fccaec97610> (1, 2, 3) {'x': 4}
- <__main__.A object at 0x7fccaec97610>
- ===================
- new <class '__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ù)中是無(wú)法實(shí)現(xiàn)的。比如說(shuō),實(shí)現(xiàn)一個(gè)大寫(xiě)的元組,代碼如下:
- 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í)是無(wú)法修改的
- # 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
本文轉(zhuǎn)載自微信公眾號(hào)「Python七號(hào)」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Python七號(hào)公眾號(hào)。