单例模式下Python调用两次__init__的问题

11
我有一个像这样的单例
class Singleton:

    class __impl:
        def __init__(self):
            print "INIT"

    __instance = None

    def __init__(self):
        # Check whether we already have an instance
        if Singleton.__instance is None:
            Singleton.__instance = Singleton.__impl()

        # Store instance reference as the only member in the handle
        self.__dict__['_Singleton__instance'] = Singleton.__instance

    def __getattr__(self, attr):
        """ Delegate access to implementation """
        return getattr(self.__instance, attr)

    def __setattr__(self, attr, value):
        """ Delegate access to implementation """
        return setattr(self.__instance, attr, value)

当我创建几个Singleton实例时,init被调用了两次,我的意思是"INIT"被打印了两次,我认为这不应该发生。
有人知道出了什么问题或者有更好的实现方法吗?

你是否了解__new__()?你似乎在做一些扭曲的事情,以便将此模式塞入__init__()中。 - Silas Ray
1
对我来说完美运行了...你能展示一下你用来测试的代码吗? - jadkik94
我尝试使用__new__,但是我无法。 - Steven Barragán
有太多的代码,我无法分享它... - Steven Barragán
1
你是否在任何情况下使用了子类化? - Winston Ewert
使用traceback.print_stack()__impl.__init__中打印堆栈,这应该会给你一些提示,为什么它被调用了两次。 - Winston Ewert
4个回答

25

以下是写一个稍微简单的Singleton模式的方法:

class Singleton(object):
    __instance = None
    def __new__(cls):
        if cls.__instance is None:
            cls.__instance = super(Singleton,cls).__new__(cls)
            cls.__instance.__initialized = False
        return cls.__instance

    def __init__(self):      
        if(self.__initialized): return
        self.__initialized = True
        print ("INIT")

a = Singleton()
b = Singleton()
print (a is b)

虽然可能有更好的方法,但我必须承认我从来不喜欢单例。我更喜欢一种工厂类型的方法:

class Foo(object):
    pass

def foo_singleton_factory(_singleton= Foo()):
    return _singleton

a = foo_singleton_factory()
b = foo_singleton_factory()
print (a is b)

这样做的优点是,如果您需要它,则可以保持获取相同的Foo实例,但是如果您在未来10年内决定不想要真正的单例,则不限于单个实例。


为什么a和b是相同的?难道它不会给你不同的单例实例吗? - code muncher
1
@codemuncher -- 是哪个版本?在第一个版本中,你会发现在第一次调用时,它设置了 cls.__instance,然后在后续的调用中不断返回相同的 cls.__instance。而在第二个版本中,它在函数创建时就创建了单例,并且在后续调用中不断返回该值(除非你传递了一个不同的 _singleton 值)。 - mgilson
第二个版本,对于我的下一个调用 b = foo_singleton_factory(),它不会创建另一个对象,我感到困惑。 - code muncher
3
默认参数只会在函数创建时被评估一次,不会在后续的调用中重新评估。这是Python中各种混淆的根源 :-). 另一个导致问题的例子可以查看https://dev59.com/9nNA5IYBdhLWcg3wAItP。 - mgilson
如果您继承Singleton类,则此方法无效。 - rajeshk
@mgilson它完美地工作,但是我对第一个版本感到困惑。 为什么它不会创建第二个实例? 谢谢 - MohammadHossein Jamshidi

10

PEP 318中提供了一个类的单例装饰器的示例:

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
    ...

(虽然我自己没有使用过它。)

顺便提一下...

我像这样制作了一个单例

此外,你应该提到你是直接从ActiveState中复制的


我会添加 def getinstance(*args, **kwargs): 如果 cls 不在 instances 中: instances[cls] = cls(*args, **kwargs) - Ofer Helman

3

既然大家都没有回答你的问题,反而提出了替代的单例实现方法,我来分享一下我的最爱。它利用了 Python 模块只会被加载一次的特性,无论你导入多少次。

同时,这个方法也遵循了 Python 的座右铭 “我们都是成年人”,因为如果你真的想要,你可以实例化多次……但这样做就需要额外的努力才能搞错。

所以在 mysingleton.py 中:

class SingletonClass(object):
    def __init__(self):
        # There's absolutely nothing special about this class
        # Nothing to see here, move along
        pass

# Defying PEP8 by capitalizing name
# This is to point out that this instance is a Singleton
Singleton = SingletonClass()

# Make it a little bit harder to use this module the wrong way
del SingletonClass

然后像这样使用它:
from mysingleton import Singleton

# Use it!

我说过,如果想要做错事情,就需要额外努力。以下是如何创建两个单例类实例,从而使其不再成为单例:

another_instance = Singleton.__class__()

那么如何避免这个问题?我引用医生的话:那就别这样做!


注意:在下面的评论之后添加了此内容

顺便说一下,这里有另一种使用元类最小化复杂代码量的单例变体:

class SingletonMeta(type):
    # All singleton methods go in the metaclass
    def a_method(cls):
        return cls.attribute

    # Special methods work too!
    def __contains__(cls, item):
        return item in cls.a_list

class Singleton(object):
    __metaclass__ = SingletonMeta
    attribute = "All attributes are class attributes"

    # Just put initialization code directly into the class
    a_list = []
    for i in range(0, 100, 3):
        a_list.append(i)

print Singleton.a_method()
print 3 in Singleton

在Python 3中,您应该这样创建单例实例:

class Singleton(metaclass=SingletonMeta):
    attribute = "One... two... five!"

现在这个有点棘手,因为单例是一个,你可以创建单例的实例。理论上这是可以的,因为即使它有实例,单例仍然是单例,但你需要记住Singleton()不是单例——Singleton才是!甚至可能符合你的需求,让单例属性作为类属性方便其实例使用。


我喜欢这个。唯一不喜欢的是你真的有一个实例,而且你不能自定义实例的创建。例如,你不能做 Singleton('foo','bar','baz')(除非你提供了 __call__,但那样你就不能用 __call__ 做其他用途了...) - mgilson
这是一个好建议。创建单例最简单的方法就是只实例化类一次! - matt b
不错,真正的单例模式。奇怪的是,在使用class AnotherClass(Singleton.__class__): pass进行删除后,它仍然可以被子类化,并且在SingletonClass中定义的函数(“方法”)如def boo(self): print 'boo!'仍然可以在AnotherClass的实例上工作。沿着这些线路,**b = Singleton.__class__()也可以工作...** isinstance(b, Singleton.__class__)(显然?)会返回True - aneroid
b = Singleton.__class__()继续:b is SingletonFalse - aneroid

1

另一种方式:

>>> class Singleton(object):
...     def __new__(cls, *args, **kwargs):
...             try:
...                     return cls._instance
...             except AttributeError:
...                     val = cls._instance = object.__new__(cls, *args, **kwargs)
...                     return val
... 
>>> class A(Singleton): pass
... 
>>> a = A()
>>> a2 = A()
>>> a2 is a
True
>>> class B(Singleton): pass
... 
>>> b = B()
>>> b2 = B()
>>> b2 is b
True
>>> b is a
False
>>> class D(Singleton):
...     def __init__(self, v): self.v = v
... 
>>> d = D(1)
>>> d.v
1

如果您担心多次调用__init__,那么选择是使用装饰器或元类。

覆盖__new__方法允许多个__init__调用,因为Python总是在返回的对象是该类的实例时调用__new__方法的__init__方法。

无论如何,我认为使用装饰器是最好的选择,因为这可能是最简单的解决方案。

如果您想了解更多在Python中创建单例的方法,请阅读this问题。

顺便说一下,如果您希望所有实例具有相同的状态(而不是身份),则可能会对Borg模式感兴趣。如果您不确定选择哪一个,请参见this答案。


我对__new__中涉及的所有魔法以及如何有效使用它并不是特别熟悉,但我的印象是,这仍然会在同一实例上多次调用__init__(这正是我认为 OP 想要避免的) 。 - mgilson
是的,@mgilson你说得对。我经常使用这种方法,因为我不太在意多个init调用。 我已经添加了一个简短的解释,说明为什么这种方法会进行多个init调用。 - Bakuriu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接