关于使用 __new__ 方法创建带锁的单例模式可能产生的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
mimzy
V2EX    Python

关于使用 __new__ 方法创建带锁的单例模式可能产生的问题

  •  
  •   mimzy
    mookrs 2018-08-17 08:54:43 +08:00 2814 次点击
    这是一个创建于 2672 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在读《编写高质量代码:改善 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 中子类和父类中方法加载的顺序,因此不太明白作者说的这两个问题是什么意思?是否有可能举出例子呢?谢谢~

    14 条回复    2018-08-17 12:09:26 +08:00
    HelloAmadeus
        1
    HelloAmadeus  
       2018-08-17 09:31:03 +08:00 via Android   1
    init 和 new 的魔法方法和普通方法表现一样。子类 override 了父类的方法,要想有父类方法的行为,必须显式的调用父类方法。还有 Python 单例一般通过模块导入实现,模块导入是线程安全的。当然也可以通过原类的__call__方法来实现。学习设计模式是学习思想,具体实现要看语言特性,不要拘泥于一种实现方式。
    yufpga
        2
    yufpga  
       2018-08-17 09:34:12 +08:00   1
    是这样的假如你有一个类继承了 Singleton, 并重载了__new__方法:

    ```
    class Derive(Singleton):

    ```
    yufpga
        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++等语言有些不同
    mimzy
        4
    mimzy  
    OP
       2018-08-17 09:51:06 +08:00
    @HelloAmadeus #1 谢谢!模块导入的确是又方便又安全的一种方法

    另外我感觉作者说的第 1 条其实是想表达 Override (重写)而不是 Overload (重载),这样就跟你的解释一致了…
    mimzy
        5
    mimzy  
    OP
       2018-08-17 09:53:53 +08:00
    @yufpga #3 谢谢!第 1 条已经看这个代码明白了~

    就是不知道第 2 条是否有更多的解释…
    Mutoo
        6
    Mutoo  
       2018-08-17 09:59:51 +08:00   1
    Singleton 可以被继承就不叫单例了。通过直接继承但不修改原有方法,就可以 fork 出另一个实例了,这已经违反了单例模式。对脚本语言来说,全局唯一实例根本不需要用面向对象的方法来保证。而 c++ 之类的静态语言可以用模版而不是继承的方式实现不同单例。
    lxy42
        7
    lxy42  
       2018-08-17 10:14:30 +08:00   1
    关于类的创建、实例的创建和实例初始化,需要掌握一点元类的知识。

    Singleton.__new__方法负责创建实例,然后 Python 内部尝试调用__init__方法初始化实例。因此,如果 Singleton 的子类定义了__init__方法,每次创建实例后 Python 都会调用__init__方法初始化实例,如果没有找到__init__方法,Python 就会一直往父类查找__init__,直至 object 为止。
    lxy42
        8
    lxy42  
       2018-08-17 10:34:55 +08:00   1
    仅供参考: https://gist.github.com/ausaki/46ec0fec6a5d3684437380a9b21e5b13

    在元类中实现单例,__init__方法只会调用一次。
    josephshen
        9
    josephshen  
       2018-08-17 10:39:27 +08:00 via iPhone   1
    请务必立马扔掉这本书或者带上强烈批判的眼镜来看,这本书质量奇差,里面有大量的严重错误,简单概念复杂化,设计模式那部分明显是带着作者 Java 背景来写的,最恐怖的事情是国内圈子居然大部分都说好,我真替他们害臊
    HelloAmadeus
        10
    HelloAmadeus  
       2018-08-17 10:43:11 +08:00 via Android   1
    @mimzy Python 是没有重载的
    mimzy
        11
    mimzy  
    OP
       2018-08-17 10:51:27 +08:00
    @josephshen #9 哈哈哈我懂,主要过一遍看看有没有遗漏的小技巧,Python Cookbook 和 Fluent Python 才是真的好~

    @HelloAmadeus #10 收到,明白~
    mimzy
        12
    mimzy  
    OP
       2018-08-17 11:17:56 +08:00
    第 2 条也理解了,# 7 的解释很详细,书中的原文这样表达可能比较好:如果子类有 __init__() 方法,那么每次实例化该**子类**的时候,__init__() 都会被调用到(按道理应该只被调用一次)。
    Hk4Fun
        13
    Hk4Fun  
       2018-08-17 11:43:15 +08:00
    用装饰器应该可以保证__init__()只被调用一次
    mimzy
        14
    mimzy  
    OP
       2018-08-17 12:09:26 +08:00 via Android
    @Hk4Fun 装饰器可能存在一个问题,用装饰器修饰的单例类不能再有子类,否则使用子类时会出错。模块导入应该是最完美的。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5634 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 35ms UTC 02:25 PVG 10:25 LAX 18:25 JFK 21:25
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86