Python 的类中,所有以双下划线__包起来的方法,叫魔术方法,魔术方法在类或对象的某些事件发出后可以自动执行,让类具有神奇的魔力,比如常见的构造方法__new__、初始化方法__init__、析构方法__del__,今天来聊一聊__new__的妙用,主要分享以下几点:
- __new__ 和 __init__ 的区别
- 应用1:改变内置的不可变类型
- 应用2:实现一个单例
- 应用3:客户端缓存
- 应用4:不同文件不同的解密方法
- 应用5:Metaclasses
__new__ 和 __init__ 的区别
1、调用时机不同:new 是真正创建实例的方法,init 用于实例的初始化,new 先于 init 运行。
2、返回值不同,new 返回一个类的实例,而 init 不返回任何信息。
3、new 是 class 的方法,而 init 是对象的方法。
示例代码:
- classA:
- def__new__(cls,*args,**kwargs):
- print("new",cls,args,kwargs)
- returnsuper().__new__(cls)
- def__init__(self,*args,**kwargs):
- print("init",self,args,kwargs)
- defhow_object_construction_works():
- x=A(1,2,3,x=4)
- print(x)
- print("===================")
- x=A.__new__(A,1,2,3,x=4)
- ifisinstance(x,A):
- type(x).__init__(x,1,2,3,x=4)
- print(x)
- if__name__=="__main__":
- how_object_construction_works()
上述代码定义了一个类 A,在调用 A(1, 2, 3, x=4) 时先执行 new,再执行 init,等价于:
- x=A.__new__(A,1,2,3,x=4)
- ifisinstance(x,A):
- type(x).__init__(x,1,2,3,x=4)
代码的运行结果如下:
-
new
'__main__.A'>(1,2,3){'x':4} - init<__main__.Aobjectat0x7fccaec97610>(1,2,3){'x':4}
- <__main__.Aobjectat0x7fccaec97610>
- ===================
-
new
'__main__.A' >(1,2,3){'x':4} - init<__main__.Aobjectat0x7fccaec97310>(1,2,3){'x':4}
- <__main__.Aobjectat0x7fccaec97310>
new 的主要作用就是让程序员可以自定义类的创建行为,以下是其主要应用场景:
应用1:改变内置的不可变类型
我们知道,元组是不可变类型,但是我们继承 tuple ,然后可以在 new 中,对其元组的元素进行修改,因为 new 返回之前,元组还不是元组,这在 init 函数中是无法实现的。比如说,实现一个大写的元组,代码如下:
- classUppercaseTuple(tuple):
- def__new__(cls,iterable):
- upper_iterable=(s.upper()forsiniterable)
- returnsuper().__new__(cls,upper_iterable)
- #以下代码会报错,初始化时是无法修改的
- #def__init__(self,iterable):
- #print(f'init{iterable}')
- #fori,arginenumerate(iterable):
- #self[i]=arg.upper()
- if__name__=='__main__':
- print("UPPERCASETUPLEEXAMPLE")
- print(UppercaseTuple(["hello","world"]))
- #UPPERCASETUPLEEXAMPLE
- #('HELLO','WORLD')
应用2:实现一个单例
- classSingleton:
- _instance=None
- def__new__(cls,*args,**kwargs):
- ifcls._instanceisNone:
- cls._instance=super().__new__(cls,*args,**kwargs)
- returncls._instance
- if__name__=="__main__":
- print("SINGLETONEXAMPLE")
- x=Singleton()
- y=Singleton()
- print(f"{xisy=}")
- #SINGLETONEXAMPLE
- #xisy=True
应用3:客户端缓存
当客户端的创建成本比较高时,比如读取文件或者数据库,可以采用以下方法,同一个客户端属于同一个实例,节省创建对象的成本,这本质就是多例模式。
- classClient:
- _loaded={}
- _db_file="file.db"
- def__new__(cls,client_id):
- if(client:=cls._loaded.get(client_id))isnotNone:
- print(f"returningexistingclient{client_id}fromcache")
- returnclient
- client=super().__new__(cls)
- cls._loaded[client_id]=client
- client._init_from_file(client_id,cls._db_file)
- returnclient
- def_init_from_file(self,client_id,file):
- #lookupclientinfileandreadproperties
- print(f"readingclient{client_id}datafromfile,db,etc.")
- name=...
- email=...
- self.name=name
- self.email=email
- self.id=client_id
- if__name__=='__main__':
- print("CLIENTCACHEEXAMPLE")
- x=Client(0)
- y=Client(0)
- print(f"{xisy=}")
- z=Client(1)
- #CLIENTCACHEEXAMPLE
- #readingclient0datafromfile,db,etc.
- #returningexistingclient0fromcache
- #xisy=True
- #readingclient1datafromfile,db,etc.
应用4:不同文件不同的解密方法
先在脚本所在目录创建三个文件:plaintext_hello.txt、rot13_hello.txt、otp_hello.txt,程序会根据不同的文件选择不同的解密算法
- importcodecs
- importitertools
- classEncryptedFile:
- _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(":///")
- ifsep:
- file=suffix
- else:
- file=prefix
- prefix="file"
- subclass=cls._registry[prefix]
- obj=object.__new__(subclass)
- obj.file=file
- obj.key=key
- returnobj
- defread(self)->str:
- raiseNotImplementedError
- classPlaintext(EncryptedFile,prefix="file"):
- defread(self):
- withopen(self.file,"r")asf:
- returnf.read()
- classROT13Text(EncryptedFile,prefix="rot13"):
- defread(self):
- withopen(self.file,"r")asf:
- text=f.read()
- returncodecs.decode(text,"rot_13")
- classOneTimePadXorText(EncryptedFile,prefix="otp"):
- def__init__(self,path,key):
- ifisinstance(self.key,str):
- self.key=self.key.encode()
- defxor_bytes_with_key(self,b:bytes)->bytes:
- returnbytes(b1^b2forb1,b2inzip(b,itertools.cycle(self.key)))
- defread(self):
- withopen(self.file,"rb")asf:
- btext=f.read()
- text=self.xor_bytes_with_key(btext).decode()
- returntext
- if__name__=="__main__":
- print("ENCRYPTEDFILEEXAMPLE")
- print(EncryptedFile("plaintext_hello.txt").read())
- print(EncryptedFile("rot13:///rot13_hello.txt").read())
- print(EncryptedFile("otp:///otp_hello.txt",key="1234").read())
- #ENCRYPTEDFILEEXAMPLE
- #plaintext_hello.txt
- #ebg13_uryyb.gkg
- #^FCkYW_X^GLE
应用5:Metaclasses
metaclass 可以像装饰器那样定制和修改继承它的子类,前文Python黑魔法之metaclass
原文链接:https://mp.weixin.qq.com/s/2XT2izhpKJ0TpYd5NUG7ig