个人总结
command.py
file.py
time.py
excel.py
with 的原理和用法
subprocess 模块
cline 提示词
PathLike
pathlib
peewee
生成随机ID
FastApi 使用 peewee
http connect
Dataframe Protocol
pyarrow
overload 函数重载
1111
peewee 线程安全
Python 日志模块
rabbitmq 问答
多进程 daemon 参数
记录异常
dict.py
多进程
忽略异常
timer.py
Ctrl+C 退出while
发布pip包
Bolo组件
迭代器与生成器
dataclass
单例模式(二)
单例模式(一)
errors.py
enum.py
每日学习
本文档使用 MrDoc 发布
-
+
首页
单例模式(二)
#### 场景及问题 我们有一个`Student`类: ``` class Student(object): def __init__(self, name): self.name = name @classmethod def create(cls, name): return cls(name) ``` 现在需要把它写成如下要求的单例模式: - 要求:参数相同时只能创建一个对象,参数不同时需要创建多个对象 根据上一章的内容,我们来分析每种写法及其存在的问题。 #### 方案1. 函数装饰器 ``` from functools import wraps def singleton(cls): instances = {} @wraps(cls) def _singleton(name): key = f"{cls}_{name}" if key not in instances: instances[key] = cls(name) return instances[key] return _singleton @singleton class Student(object): pass ``` 这个方法可以创建单例对象,但是它有个明显的问题: - 你无法再调用`Student`的`类函数` 当你调用`Student.create`方法的时候,运行时会报错: ``` # 没有wraps(cls) AttributeError: 'function' object has no attribute 'create' # 加了wraps(cls) TypeError: 'classmethod' object is not callable ``` 此时的`Student`其实是`_singleton`函数,你当然不能对函数调用`create`方法。 这个问题在这个方案中是无解的。 >d 结论:该方案不可行 #### 方案2. 类装饰器 普通的`类装饰器`也会有上面的问题,但是重写`__getattr__`方法就可以解决这个问题 ``` class SingleTon(object): _instances = {} def __init__(self, cls): self.cls = cls def __call__(self, name, *args, **kwargs): key = f"{self.cls}_{name}" if key not in SingleTon._instances: SingleTon._instances[key] = self.cls(name, *args, **kwargs) return SingleTon._instances.get(key) def __getattr__(self, name: str): return getattr(self.cls, name) @SingleTon class Student(object): pass ``` - 为什么要重写`__getattr__`方法? 在没有重写该方法的情况下,有以下等价: ``` Student == SingleTon(Student) Student() == SingleTon(Student)() Student.create() == SingleTon(Student).create() # 调用时会报错,因为SingleTon对象没有create方法 Student.__name__ == "SingleTon" # 这个不是我们希望的 ``` 从这里可以看出,`Student`其实已经被隐藏,当你调用`Student.create()`方法的时候,程序将报错,因为`SingleTon`的对象没有该方法。 - 重写`__getattr__`方法后,可以解决上面提到的问题: - `类的签名`变正确了:`Student.__name__`显示为`Student`; - `类方法`和`类属性`都可以正常使用了:`Student.create` 方法可以正常调用; >s 结论:该方案可行,没有任何问题 #### 方案3. 通过`__new__`方法来实现单例 这种方法也是可行的,但是需要注意`防止重复初始化` > 因为每次通过类来创建对象的时候都会执行初始化函数,即便我们改写了`__new__`方法,并没有真的重新创建对象,但是初始化函数总是会在`__new__`方法之后执行。所以我们需要防止重复初始化。 ``` class Student(object): _instances = {} def __new__(cls, name, *args, **kwargs): key = f"{self.cls}_{name}" if not cls._instances.get(key): cls._instances[key] = super().__new__(cls) cls._instances[key].init(name) return cls._instances[key] def __init__(self, *args, **kwargs): """该函数为空,防止单例对象被重复初始化""" super().__init__() def init(self, name): self.name = name @classmethod def create(cls, name): return cls(name) ``` 所以这个方案也会有一个问题: - 由于`__init__`方法被废弃,因此不能在这里定义类对象的属性,只能在`init`方法中定义类对象的属性,有些ide会有告警提示 >w 结论:该方案可行,但是有点小问题 #### 方案4. 使用函数来创建对象 这个是最简单的方式,而且也不会出现上面的问题 ``` _instances = {} def create_student(name): key = f"{name}" r = _instances.get(key) if not r: _instances[key] = Student(name) return _instances[key] ``` 但是这个方案也会有它自己的问题: - 缺乏约束,无法避免不熟悉的开发者通过类直接创建实例; >w 结论:该方案可行,但是有点小问题 #### 综述 看起来,比较完美的方案只有`类装饰器`了,其他方案都会或多或少有点问题
gaojian
2022年12月16日 11:35
分享文档
收藏文档
上一篇
下一篇
微信扫一扫
复制链接
手机扫一扫进行分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码