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

作者:somenzz 时间:2021-04-06 04:45:56 

目录
  • 1、概述

  • 2、__new__ 和 __init__ 的区别

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

  • 4、应用2:实现一个单例

  • 5、应用3:客户端缓存

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

1、概述

python 的类中,所有以双下划线__包起来的方法,叫魔术方法,魔术方法在类或对象的某些事件发出后可以自动执行,让类具有神奇的魔力,比如常见的构造方法__new__ 、初始化方法__init__ 、析构方法__del__ ,今天来聊一聊__new__的妙用,主要分享以下几点:

  • __new__ 和 __init__ 的区别

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

  • 应用2:实现一个单例

  • 应用3:客户端缓存

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

  • 应用5:Metaclasses

2、__new__ 和 __init__ 的区别

  • 调用时机不同:new 是真正创建实例的方法,init 用于实例的初始化,new 先于 init 运行。

  • 返回值不同,new 返回一个类的实例,而 init 不返回任何信息。

  • new class 的方法,而 init 是对象的方法。

示例代码:


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()

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


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

代码的运行结果如下:


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 的主要作用就是让程序员可以自定义类的创建行为,以下是其主要应用场景:

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

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


class UppercaseTuple(tuple):
   def __new__(cls, iterable):
       upper_iterable = (s.upper() for s in iterable)
       return super().__new__(cls, upper_iterable)

# 以下代码会报错,初始化时是无法修改的
   # 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')

4、应用2:实现一个单例


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

5、应用3:客户端缓存

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


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.

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

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


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

来源:https://developer.51cto.com/art/202110/686454.htm

标签:Python,New
0
投稿

猜你喜欢

  • python爬虫selenium和phantomJs使用方法解析

    2023-02-06 19:23:38
  • 在asp里通过以下两个函数实现javascript里的escape函数和unescape函数功能

    2010-03-14 11:30:00
  • python之yield表达式学习

    2022-05-06 14:51:43
  • Java连接Mysql 8.0.18版本的方法详解

    2024-01-24 10:08:29
  • Pycharm配置远程调试的方法步骤

    2021-03-13 00:45:38
  • Python新手如何进行闭包时绑定变量操作

    2021-05-01 15:23:55
  • yolov5中train.py代码注释详解与使用教程

    2022-12-04 00:55:03
  • 教你制作IBM LOGO的方法

    2007-10-23 13:34:00
  • Python实现的使用telnet登陆聊天室实例

    2023-11-17 10:41:17
  • 使用Golang的Context管理上下文的方法

    2023-06-29 06:37:23
  • pyinstaller打包多个py文件和去除cmd黑框的方法

    2022-12-18 13:52:30
  • Python如何快速上手? 快速掌握一门新语言的方法

    2023-05-07 12:00:48
  • python变量赋值机制踩坑记录

    2021-08-19 08:59:44
  • sqlserver 导出插入脚本代码

    2024-01-26 22:39:18
  • Mybatis如何自动生成数据库表结构总结

    2024-01-21 11:04:30
  • Python中全局变量和局部变量的理解与区别

    2022-11-13 15:35:14
  • Python新手学习标准库模块命名

    2021-01-16 05:18:06
  • position、z-index、top、right、bottom和left属性

    2009-10-04 20:33:00
  • Django上线部署之IIS的配置方法

    2023-02-18 05:17:52
  • Python标准库之typing的用法(类型标注)

    2021-09-27 01:25:24
  • asp之家 网络编程 m.aspxhome.com