最近在读《编写高质量代码:改善 Python 程序的 91 个建议》这本书,我在作者给出的双检查锁单例模式基础上做了一点改写,精简了冗余的部分,如下:
import threading
class Singleton:
_instances = {}
_instance_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if cls not in cls._instances:
with cls._instance_lock:
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instances[cls]
但是作者指出这个版本的单例有两个问题:
Singleton
的子类重载了 __new__()
方法,会覆盖或干扰 Singleton
类中 __new__()
的执行,虽然这种情况出现的概率极小,但不可忽视。__init__()
方法,那么每次实例化该 Singleton
的时候,__init__()
都会被调用到,这显然是不应该的,__init__()
只应该在创建实例的时候被调用一次。我不太理解 Python 中子类和父类中方法加载的顺序,因此不太明白作者说的这两个问题是什么意思?是否有可能举出例子呢?谢谢~
1
HelloAmadeus 2018-08-17 09:31:03 +08:00 via Android 1
init 和 new 的魔法方法和普通方法表现一样。子类 override 了父类的方法,要想有父类方法的行为,必须显式的调用父类方法。还有 Python 单例一般通过模块导入实现,模块导入是线程安全的。当然也可以通过原类的__call__方法来实现。学习设计模式是学习思想,具体实现要看语言特性,不要拘泥于一种实现方式。
|
2
yufpga 2018-08-17 09:34:12 +08:00 1
是这样的假如你有一个类继承了 Singleton, 并重载了__new__方法:
``` class Derive(Singleton): ``` |
3
yufpga 2018-08-17 09:43:47 +08:00 1
是这样的假如你有一个类继承了 Singleton, 并重载了__new__方法:
``` class Derive(Singleton): def __new__(cls): # super().__new__() # 不小心忘记了 pass ``` 如果你在子类的__new__方法中忘记这是一个单例类, 你很可能会忘记显式的执行父类中的__new__方法,这时候父类中单例的那部分逻辑是不会执行的, 这时候 Derive 创建对象并不是单例的,这显然与你的预期是不符和的。 在 Python 中,由于 Python 的 import 机制和文件作用域,因此建议通过此来实现单例,这个和 c++等语言有些不同 |
4
mimzy OP @HelloAmadeus #1 谢谢!模块导入的确是又方便又安全的一种方法
另外我感觉作者说的第 1 条其实是想表达 Override (重写)而不是 Overload (重载),这样就跟你的解释一致了… |
6
Mutoo 2018-08-17 09:59:51 +08:00 1
Singleton 可以被继承就不叫单例了。通过直接继承但不修改原有方法,就可以 fork 出另一个实例了,这已经违反了单例模式。对脚本语言来说,全局唯一实例根本不需要用面向对象的方法来保证。而 c++ 之类的静态语言可以用模版而不是继承的方式实现不同单例。
|
7
lxy42 2018-08-17 10:14:30 +08:00 1
关于类的创建、实例的创建和实例初始化,需要掌握一点元类的知识。
Singleton.__new__方法负责创建实例,然后 Python 内部尝试调用__init__方法初始化实例。因此,如果 Singleton 的子类定义了__init__方法,每次创建实例后 Python 都会调用__init__方法初始化实例,如果没有找到__init__方法,Python 就会一直往父类查找__init__,直至 object 为止。 |
8
lxy42 2018-08-17 10:34:55 +08:00 1
|
9
josephshen 2018-08-17 10:39:27 +08:00 via iPhone 1
请务必立马扔掉这本书或者带上强烈批判的眼镜来看,这本书质量奇差,里面有大量的严重错误,简单概念复杂化,设计模式那部分明显是带着作者 Java 背景来写的,最恐怖的事情是国内圈子居然大部分都说好,我真替他们害臊
|
10
HelloAmadeus 2018-08-17 10:43:11 +08:00 via Android 1
@mimzy Python 是没有重载的
|
11
mimzy OP @josephshen #9 哈哈哈我懂,主要过一遍看看有没有遗漏的小技巧,Python Cookbook 和 Fluent Python 才是真的好~
@HelloAmadeus #10 收到,明白~ |
12
mimzy OP 第 2 条也理解了,# 7 的解释很详细,书中的原文这样表达可能比较好:如果子类有 __init__() 方法,那么每次实例化该**子类**的时候,__init__() 都会被调用到(按道理应该只被调用一次)。
|
13
Hk4Fun 2018-08-17 11:43:15 +08:00
用装饰器应该可以保证__init__()只被调用一次
|