Python 中类的构造方法 __New__ 的妙用

时间:2021-12-04 06:35:21

Python 中类的构造方法 __New__ 的妙用

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 是对象的方法。

示例代码:

  1. classA:
  2. def__new__(cls,*args,**kwargs):
  3. print("new",cls,args,kwargs)
  4. returnsuper().__new__(cls)
  5. def__init__(self,*args,**kwargs):
  6. print("init",self,args,kwargs)
  7. defhow_object_construction_works():
  8. x=A(1,2,3,x=4)
  9. print(x)
  10. print("===================")
  11. x=A.__new__(A,1,2,3,x=4)
  12. ifisinstance(x,A):
  13. type(x).__init__(x,1,2,3,x=4)
  14. print(x)
  15. if__name__=="__main__":
  16. how_object_construction_works()

上述代码定义了一个类 A,在调用 A(1, 2, 3, x=4) 时先执行 new,再执行 init,等价于:

  1. x=A.__new__(A,1,2,3,x=4)
  2. ifisinstance(x,A):
  3. type(x).__init__(x,1,2,3,x=4)

代码的运行结果如下:

  1. new'__main__.A'>(1,2,3){'x':4}
  2. init<__main__.Aobjectat0x7fccaec97610>(1,2,3){'x':4}
  3. <__main__.Aobjectat0x7fccaec97610>
  4. ===================
  5. new'__main__.A'>(1,2,3){'x':4}
  6. init<__main__.Aobjectat0x7fccaec97310>(1,2,3){'x':4}
  7. <__main__.Aobjectat0x7fccaec97310>

new 的主要作用就是让程序员可以自定义类的创建行为,以下是其主要应用场景:

应用1:改变内置的不可变类型

我们知道,元组是不可变类型,但是我们继承 tuple ,然后可以在 new 中,对其元组的元素进行修改,因为 new 返回之前,元组还不是元组,这在 init 函数中是无法实现的。比如说,实现一个大写的元组,代码如下:

  1. classUppercaseTuple(tuple):
  2. def__new__(cls,iterable):
  3. upper_iterable=(s.upper()forsiniterable)
  4. returnsuper().__new__(cls,upper_iterable)
  5. #以下代码会报错,初始化时是无法修改的
  6. #def__init__(self,iterable):
  7. #print(f'init{iterable}')
  8. #fori,arginenumerate(iterable):
  9. #self[i]=arg.upper()
  10. if__name__=='__main__':
  11. print("UPPERCASETUPLEEXAMPLE")
  12. print(UppercaseTuple(["hello","world"]))
  13. #UPPERCASETUPLEEXAMPLE
  14. #('HELLO','WORLD')

应用2:实现一个单例

  1. classSingleton:
  2. _instance=None
  3. def__new__(cls,*args,**kwargs):
  4. ifcls._instanceisNone:
  5. cls._instance=super().__new__(cls,*args,**kwargs)
  6. returncls._instance
  7. if__name__=="__main__":
  8. print("SINGLETONEXAMPLE")
  9. x=Singleton()
  10. y=Singleton()
  11. print(f"{xisy=}")
  12. #SINGLETONEXAMPLE
  13. #xisy=True

应用3:客户端缓存

当客户端的创建成本比较高时,比如读取文件或者数据库,可以采用以下方法,同一个客户端属于同一个实例,节省创建对象的成本,这本质就是多例模式。

  1. classClient:
  2. _loaded={}
  3. _db_file="file.db"
  4. def__new__(cls,client_id):
  5. if(client:=cls._loaded.get(client_id))isnotNone:
  6. print(f"returningexistingclient{client_id}fromcache")
  7. returnclient
  8. client=super().__new__(cls)
  9. cls._loaded[client_id]=client
  10. client._init_from_file(client_id,cls._db_file)
  11. returnclient
  12. def_init_from_file(self,client_id,file):
  13. #lookupclientinfileandreadproperties
  14. print(f"readingclient{client_id}datafromfile,db,etc.")
  15. name=...
  16. email=...
  17. self.name=name
  18. self.email=email
  19. self.id=client_id
  20. if__name__=='__main__':
  21. print("CLIENTCACHEEXAMPLE")
  22. x=Client(0)
  23. y=Client(0)
  24. print(f"{xisy=}")
  25. z=Client(1)
  26. #CLIENTCACHEEXAMPLE
  27. #readingclient0datafromfile,db,etc.
  28. #returningexistingclient0fromcache
  29. #xisy=True
  30. #readingclient1datafromfile,db,etc.

应用4:不同文件不同的解密方法

先在脚本所在目录创建三个文件:plaintext_hello.txt、rot13_hello.txt、otp_hello.txt,程序会根据不同的文件选择不同的解密算法

  1. importcodecs
  2. importitertools
  3. classEncryptedFile:
  4. _registry={}#'rot13'->ROT13Text
  5. def__init_subclass__(cls,prefix,**kwargs):
  6. super().__init_subclass__(**kwargs)
  7. cls._registry[prefix]=cls
  8. def__new__(cls,path:str,key=None):
  9. prefix,sep,suffix=path.partition(":///")
  10. ifsep:
  11. file=suffix
  12. else:
  13. file=prefix
  14. prefix="file"
  15. subclass=cls._registry[prefix]
  16. obj=object.__new__(subclass)
  17. obj.file=file
  18. obj.key=key
  19. returnobj
  20. defread(self)->str:
  21. raiseNotImplementedError
  22. classPlaintext(EncryptedFile,prefix="file"):
  23. defread(self):
  24. withopen(self.file,"r")asf:
  25. returnf.read()
  26. classROT13Text(EncryptedFile,prefix="rot13"):
  27. defread(self):
  28. withopen(self.file,"r")asf:
  29. text=f.read()
  30. returncodecs.decode(text,"rot_13")
  31. classOneTimePadXorText(EncryptedFile,prefix="otp"):
  32. def__init__(self,path,key):
  33. ifisinstance(self.key,str):
  34. self.key=self.key.encode()
  35. defxor_bytes_with_key(self,b:bytes)->bytes:
  36. returnbytes(b1^b2forb1,b2inzip(b,itertools.cycle(self.key)))
  37. defread(self):
  38. withopen(self.file,"rb")asf:
  39. btext=f.read()
  40. text=self.xor_bytes_with_key(btext).decode()
  41. returntext
  42. if__name__=="__main__":
  43. print("ENCRYPTEDFILEEXAMPLE")
  44. print(EncryptedFile("plaintext_hello.txt").read())
  45. print(EncryptedFile("rot13:///rot13_hello.txt").read())
  46. print(EncryptedFile("otp:///otp_hello.txt",key="1234").read())
  47. #ENCRYPTEDFILEEXAMPLE
  48. #plaintext_hello.txt
  49. #ebg13_uryyb.gkg
  50. #^FCkYW_X^GLE

应用5:Metaclasses

metaclass 可以像装饰器那样定制和修改继承它的子类,前文Python黑魔法之metaclass

原文链接:https://mp.weixin.qq.com/s/2XT2izhpKJ0TpYd5NUG7ig