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. class A: 
  2.     def __new__(cls, *args, **kwargs): 
  3.         print("new", cls, args, kwargs) 
  4.         return super().__new__(cls) 
  5.  
  6.     def __init__(self, *args, **kwargs): 
  7.         print("init", self, args, kwargs) 
  8.  
  9.  
  10. def how_object_construction_works(): 
  11.     x = A(1, 2, 3, x=4) 
  12.     print(x)     
  13.     print("===================") 
  14.     x = A.__new__(A, 1, 2, 3, x=4) 
  15.     if isinstance(x, A): 
  16.         type(x).__init__(x, 1, 2, 3, x=4) 
  17.     print(x) 
  18.  
  19. if __name__ == "__main__": 
  20.     how_object_construction_works() 

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

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

代码的运行结果如下:

  1. new <class '__main__.A'> (1, 2, 3) {'x': 4} 
  2. init <__main__.A object at 0x7fccaec97610> (1, 2, 3) {'x': 4} 
  3. <__main__.A object at 0x7fccaec97610> 
  4. =================== 
  5. new <class '__main__.A'> (1, 2, 3) {'x': 4} 
  6. init <__main__.A object at 0x7fccaec97310> (1, 2, 3) {'x': 4} 
  7. <__main__.A object at 0x7fccaec97310> 

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

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

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

  1. class UppercaseTuple(tuple): 
  2.     def __new__(cls, iterable): 
  3.         upper_iterable = (s.upper() for s in iterable) 
  4.         return super().__new__(cls, upper_iterable) 
  5.  
  6.     # 以下代码会报错,初始化时是无法修改的 
  7.     # def __init__(self, iterable): 
  8.     #     print(f'init {iterable}') 
  9.     #     for i, arg in enumerate(iterable): 
  10.     #         self[i] = arg.upper() 
  11.  
  12. if __name__ == '__main__': 
  13.     print("UPPERCASE TUPLE EXAMPLE") 
  14.     print(UppercaseTuple(["hello", "world"])) 
  15.  
  16. # UPPERCASE TUPLE EXAMPLE 
  17. # ('HELLO', 'WORLD') 

应用2:实现一个单例

  1. class Singleton: 
  2.     _instance = None 
  3.  
  4.     def __new__(cls, *args, **kwargs): 
  5.         if cls._instance is None: 
  6.             cls._instance = super().__new__(cls, *args, **kwargs) 
  7.         return cls._instance 
  8.  
  9.  
  10. if __name__ == "__main__": 
  11.  
  12.     print("SINGLETON EXAMPLE") 
  13.     x = Singleton() 
  14.     y = Singleton() 
  15.     print(f"{x is y=}") 
  16. # SINGLETON EXAMPLE 
  17. # x is y=True 

应用3:客户端缓存

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

  1. class Client: 
  2.     _loaded = {} 
  3.     _db_file = "file.db" 
  4.  
  5.     def __new__(cls, client_id): 
  6.         if (client := cls._loaded.get(client_id)) is not None: 
  7.             print(f"returning existing client {client_id} from cache") 
  8.             return client 
  9.         client = super().__new__(cls) 
  10.         cls._loaded[client_id] = client 
  11.         client._init_from_file(client_id, cls._db_file) 
  12.         return client 
  13.  
  14.     def _init_from_file(self, client_id, file): 
  15.         # lookup client in file and read properties 
  16.         print(f"reading client {client_id} data from file, db, etc.") 
  17.         name = ... 
  18.         email = ... 
  19.         self.name = name 
  20.         self.email = email 
  21.         self.id = client_id 
  22.  
  23.  
  24. if __name__ == '__main__': 
  25.     print("CLIENT CACHE EXAMPLE") 
  26.     x = Client(0) 
  27.     y = Client(0) 
  28.     print(f"{x is y=}") 
  29.     z = Client(1) 
  30. # CLIENT CACHE EXAMPLE 
  31. # reading client 0 data from file, db, etc. 
  32. # returning existing client 0 from cache 
  33. # x is y=True 
  34. # reading client 1 data from file, db, etc. 

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

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

  1. import codecs 
  2. import itertools 
  3.  
  4.  
  5. class EncryptedFile: 
  6.     _registry = {}  # 'rot13' -> ROT13Text 
  7.  
  8.     def __init_subclass__(cls, prefix, **kwargs): 
  9.         super().__init_subclass__(**kwargs) 
  10.         cls._registry[prefix] = cls 
  11.  
  12.     def __new__(cls, path: str, key=None): 
  13.         prefix, sep, suffix = path.partition(":///") 
  14.         if sep: 
  15.             file = suffix 
  16.         else: 
  17.             file = prefix 
  18.             prefix = "file" 
  19.         subclass = cls._registry[prefix] 
  20.         obj = object.__new__(subclass) 
  21.         obj.file = file 
  22.         obj.key = key 
  23.         return obj 
  24.  
  25.     def read(self) -> str: 
  26.         raise NotImplementedError 
  27.  
  28.  
  29. class Plaintext(EncryptedFile, prefix="file"): 
  30.     def read(self): 
  31.         with open(self.file, "r") as f: 
  32.             return f.read() 
  33.  
  34.  
  35. class ROT13Text(EncryptedFile, prefix="rot13"): 
  36.     def read(self): 
  37.         with open(self.file, "r") as f: 
  38.             text = f.read() 
  39.         return codecs.decode(text, "rot_13") 
  40.  
  41.  
  42. class OneTimePadXorText(EncryptedFile, prefix="otp"): 
  43.     def __init__(self, path, key): 
  44.         if isinstance(self.key, str): 
  45.             self.key = self.key.encode() 
  46.  
  47.     def xor_bytes_with_key(self, b: bytes) -> bytes: 
  48.         return bytes(b1 ^ b2 for b1, b2 in zip(b, itertools.cycle(self.key))) 
  49.  
  50.     def read(self): 
  51.         with open(self.file, "rb") as f: 
  52.             btext = f.read() 
  53.         text = self.xor_bytes_with_key(btext).decode() 
  54.         return text 
  55.  
  56.  
  57. if __name__ == "__main__": 
  58.     print("ENCRYPTED FILE EXAMPLE") 
  59.     print(EncryptedFile("plaintext_hello.txt").read()) 
  60.     print(EncryptedFile("rot13:///rot13_hello.txt").read()) 
  61.     print(EncryptedFile("otp:///otp_hello.txt", key="1234").read()) 
  62. # ENCRYPTED FILE EXAMPLE 
  63. # plaintext_hello.txt 
  64. # ebg13_uryyb.gkg 
  65. # ^FCkYW_X^GLE 

应用5:Metaclasses

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

评论关闭