有没有一种简单、优雅的方式来定义单例? [复制]

Posted

技术标签:

【中文标题】有没有一种简单、优雅的方式来定义单例? [复制]【英文标题】:Is there a simple, elegant way to define singletons? [duplicate] 【发布时间】:2010-09-07 02:36:10 【问题描述】:

在 Python 中定义singletons 的方法似乎有很多。是否对 *** 达成共识?

【问题讨论】:

Singletons are Pathological Liars,不是吗? “这个问题不适合我们的问答格式” - 我认为这不是一个主观问题,有没有办法提出这样的问题,使其符合 SO Q&A 格式? 我不同意这没有建设性。如果移到programmers.stackexchange.com 可以重新打开吗? @***www 否,因为它是基于意见的,progs.SE 不喜欢这样。 @ratchetfreak 这个问题受欢迎的原因是像我这样的人正在寻找不同的方法在 python 中创建单例。有一些有利有弊的替代方案,或者仅适用于某些情况。这个问题可以重新表述为“Python 中存在哪些不同的方法来创建单例?我对基于类的解决方案和基于类实例的解决方案之间的区别特别感兴趣。” 【参考方案1】:

有一次我在 Python 中编写了一个单例,我使用了一个类,其中所有成员函数都有 classmethod 装饰器。

class Foo:
    x = 1
  
    @classmethod
    def increment(cls, y=1):
        cls.x += y

【讨论】:

我喜欢这种方法,但有一个小问题。至少在 Python 2.6 中,您不能将 __len____getitem__ 之类的方法用作类方法,因此您没有像使用对象那样灵活地进行自定义。由于我经常想使用 Singleton 作为数据的集合,这有点令人失望。 在我看来,这只不过是将一堆东西包装到一个命名空间中......并不是说这有什么问题,有些人甚至说他们认为这是一个很棒的主意(import this) -- 只是这种方法并不简单,而且似乎非常接近于使用通常被认为是不好的工程实践的全局变量。 @martineau 我建议无论如何实现,使用单例都非常接近使用全局变量。 单例在两个方面优于全局变量:它们根本不会污染全局命名空间(或者像你的答案一样多),并且它们还提供惰性评估,而全局变量通常不要(你的答案也没有)。 @DanHomerick 对于__len____getitem__ 甚至@property,您可以将__metaclass__ 设置为定义上述内容的类。工作很棒。我投票支持一个单例类,这是语言的设计,是它的元类的一个实例。实际上,所有方法都可以在元类中定义,然后该类将仅用作对单例的引用【参考方案2】:

我对此非常不确定,但我的项目使用“约定单例”(不是强制单例),也就是说,如果我有一个名为 DataController 的类,我会在同一个模块中定义它:

_data_controller = None
def GetDataController():
    global _data_controller
    if _data_controller is None:
        _data_controller = DataController()
    return _data_controller

它并不优雅,因为它是整整六行。但是我所有的单身人士都使用这种模式,而且它至少非常明确(这是 pythonic)。

【讨论】:

+1 在 Python 中,一切都应该与约定有关(因为您通常可以绕过强制边界)。就个人而言,我更喜欢使用类方法和类变量来访问和存储实例,因此您不必使用global。 (我通常不鼓励使用global,尽管这是可以接受的少数用例之一。) 应该DataController_DataController 吗?否则可以直接实例化它 这是我认为最好的解决方案,因为它是你以后遇到代码时最容易理解的。【参考方案3】:

这是我自己的单例实现。您所要做的就是装饰班级;要获得单例,您必须使用 Instance 方法。这是一个例子:

@Singleton
class Foo:
   def __init__(self):
       print 'Foo created'

f = Foo() # Error, this isn't how you get the instance of a singleton

f = Foo.instance() # Good. Being explicit is in line with the Python Zen
g = Foo.instance() # Returns already created instance

print f is g # True

这是代码:

class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Also, the decorated class cannot be
    inherited from. Other than that, there are no restrictions that apply
    to the decorated class.

    To get the singleton instance, use the `instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

【讨论】:

包含电池的 Python 这应该是 desing_pattern 标准库的一部分,谢谢 @akhan 我决定故意不支持带参数的构造函数,因为参数只会在第一次使用,而在其他所有时间都被忽略。这会使您的代码很难遵循,因为您可能在不同的地方使用不同的参数,但您可能不知道这些调用中的哪一个是实际初始化单例的调用。 @akhan 如果你真的想用参数初始化你的单例,你应该有一个单独的initialize() 方法,它可以接受任何参数并在多次调用时抛出。 这是一个非常糟糕的单例实现。首先,它不是一个合适的装饰器,因为它不使用functools.wrapsfunctools.update_wrapper。其次,必须通过调用 Foo.Instance() 来获取实例是非常不符合 Python 标准的,而且它不能被实现为 Foo() 的原因正好是 0 个。第三,像这样替换类会产生意想不到的结果,例如type(Foo.instance()) is Foo -> False @Aran-Fey 似乎这个解决方案真的打破了你的泡沫哈哈。我不相信 Paul Manta 曾经说过这是全世界最好的解决方案。他只是想回答原作者的问题。我认为这是解决 python 中“缺乏”的一个很好的解决方案。【参考方案4】:

单身的同父异母兄弟

我完全同意 staale,我在这里留下一个创建单身同父异母兄弟的示例:

class void:pass
a = void();
a.__class__ = Singleton

a 现在将报告为与单例相同的类,即使它看起来不像。因此,使用复杂类的单例最终取决于我们不会对它们造成太大影响。

这样,我们可以得到同样的效果并使用更简单的东西,比如变量或模块。尽管如此,如果我们为了清楚起见想要使用类,并且因为在 Python 中类是一个对象,所以我们已经有了对象(不是实例,但它会这样做)。

class Singleton:
    def __new__(cls): raise AssertionError # Singletons can't have instances

如果我们尝试创建一个实例,我们会遇到一个很好的断言错误,我们可以存储派生静态成员并在运行时对它们进行更改(我喜欢 Python)。这个对象和其他大约半兄弟一样好(如果你愿意,你仍然可以创建它们),但是由于简单,它会运行得更快。

【讨论】:

【参考方案5】:

Python documentation 确实涵盖了这一点:

class Singleton(object):
    def __new__(cls, *args, **kwds):
        it = cls.__dict__.get("__it__")
        if it is not None:
            return it
        cls.__it__ = it = object.__new__(cls)
        it.init(*args, **kwds)
        return it
    def init(self, *args, **kwds):
        pass

我可能会重写它,让它看起来更像这样:

class Singleton(object):
    """Use to create a singleton"""
    def __new__(cls, *args, **kwds):
        """
        >>> s = Singleton()
        >>> p = Singleton()
        >>> id(s) == id(p)
        True
        """
        self = "__self__"
        if not hasattr(cls, self):
            instance = object.__new__(cls)
            instance.init(*args, **kwds)
            setattr(cls, self, instance)
        return getattr(cls, self)

    def init(self, *args, **kwds):
        pass

扩展这个应该比较干净:

class Bus(Singleton):
    def init(self, label=None, *args, **kwds):
        self.label = label
        self.channels = [Channel("system"), Channel("app")]
        ...

【讨论】:

+1 是唯一提到 Guido van Rossum 实现的人。但是你自己的版本是错误的:你不应该在__new__ 中使用hasattrgetattr,因为它们都调用object.__getattribute__,这反过来又在所有类层次结构中查找你的"__self__" 属性,而不仅仅是当前类.如果 Guido 使用 __dict__ 进行属性访问,那是有原因的。试试:class A(GuidoSingleton): passclass B(A): passclass C(YourSingleton): passclass D(C): passprint(A(), B(), C(), D())。所有子类使用YourSingleton! 引用同一个实例 +1 提醒我们 Python 文档始终是开始搜索单例和其他设计模式的最佳位置。【参考方案6】:

好吧,我知道单身可能是好是坏。这是我的实现,我只是扩展了一种经典方法,在内部引入缓存并生成许多不同类型的实例,或者许多相同类型但具有不同参数的实例。

我将其命名为 Singleton_group,因为它将相似的实例组合在一起并防止创建具有相同参数的同一类的对象:

# Peppelinux's cached singleton
class Singleton_group(object):
    __instances_args_dict = 
    def __new__(cls, *args, **kwargs):
        if not cls.__instances_args_dict.get((cls.__name__, args, str(kwargs))):
            cls.__instances_args_dict[(cls.__name__, args, str(kwargs))] = super(Singleton_group, cls).__new__(cls, *args, **kwargs)
        return cls.__instances_args_dict.get((cls.__name__, args, str(kwargs)))


# It's a dummy real world use example:
class test(Singleton_group):
    def __init__(self, salute):
        self.salute = salute

a = test('bye')
b = test('hi')
c = test('bye')
d = test('hi')
e = test('goodbye')
f = test('goodbye')

id(a)
3070148780L

id(b)
3070148908L

id(c)
3070148780L

b == d
True


b._Singleton_group__instances_args_dict

('test', ('bye',), ''): <__main__.test object at 0xb6fec0ac>,
 ('test', ('goodbye',), ''): <__main__.test object at 0xb6fec32c>,
 ('test', ('hi',), ''): <__main__.test object at 0xb6fec12c>

每个对象都带有单例缓存...这可能是邪恶的,但它对某些人来说非常有用:)

【讨论】:

【参考方案7】:

如果您不想要上述基于元类的解决方案,并且您不喜欢简单的基于函数装饰器的方法(例如,因为在这种情况下单例类上的静态方法将不起作用),这妥协作品:

class singleton(object):
  """Singleton decorator."""

  def __init__(self, cls):
      self.__dict__['cls'] = cls

  instances = 

  def __call__(self):
      if self.cls not in self.instances:
          self.instances[self.cls] = self.cls()
      return self.instances[self.cls]

  def __getattr__(self, attr):
      return getattr(self.__dict__['cls'], attr)

  def __setattr__(self, attr, value):
      return setattr(self.__dict__['cls'], attr, value)

【讨论】:

【参考方案8】:

我认为强制一个类或实例成为单例是过分的。就个人而言,我喜欢定义一个普通的可实例化类、一个半私有引用和一个简单的工厂函数。

class NothingSpecial:
    pass

_the_one_and_only = None

def TheOneAndOnly():
    global _the_one_and_only
    if not _the_one_and_only:
        _the_one_and_only = NothingSpecial()
    return _the_one_and_only

或者如果在第一次导入模块时实例化没有问题:

class NothingSpecial:
    pass

THE_ONE_AND_ONLY = NothingSpecial()

这样您就可以针对新实例编写测试而不会产生副作用,并且无需在模块中散布全局语句,并且如果需要,您可以在将来派生变体。

【讨论】:

我认为这比矫枉过正更糟糕。强制隐式行为不是pythonic。而且我有更多关于重复答案的 cmets :-) ***.com/questions/31875/…【参考方案9】:

参见PEP318 的这个实现,用装饰器实现单例模式:

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

@singleton
class MyClass:
    ...

【讨论】:

这个装饰器的问题是'MyClass'不再是一个类了,例如super() 不起作用,类方法不起作用等:@singleton class MyClass(BaseClass): def __init__(self): super(MyClass, self).__init__() 看来装饰器应该应用于new方法,而不是类,以处理继承问题。到那时,装饰器的优雅可读性就会降低。或者装饰器需要与它正在装饰的类进行摩擦,以使 new 函数做一些明智的事情。【参考方案10】:

在 Python 中实现单例的一个稍微不同的方法是 Alex Martelli(Google 员工和 Python 天才)的 borg pattern。

class Borg:
    __shared_state = 
    def __init__(self):
        self.__dict__ = self.__shared_state

因此,它们不是强制所有实例具有相同的身份,而是共享状态。

【讨论】:

也称为单态。可能比单身更邪恶。 不适用于新样式类 有人能解释为什么这不适用于新式类吗? @JamesEmerton:我刚刚在 Python 2.7.2 上试过,可以很好地使用新样式类。 @pylover:你说得对,它不是单例——这可能是Alex Martelli 给它起不同名字的部分原因——但它的效果非常相似。【参考方案11】:

对于 Python 来说相对较新,我不确定最常见的习惯用法是什么,但我能想到的最简单的事情就是使用模块而不是类。类上的实例方法变成了模块中的函数,任何数据都变成了模块中的变量,而不是类的成员。我怀疑这是解决人们使用单例的问题类型的 Pythonic 方法。

如果你真的想要一个单例类,first hit on Google 上为“Python 单例”描述了一个合理的实现,具体来说:

class Singleton:
    __single = None
    def __init__( self ):
        if Singleton.__single:
            raise Singleton.__single
        Singleton.__single = self

这似乎可以解决问题。

【讨论】:

不好,只是引发异常而不是返回单例实例【参考方案12】:

我真的不认为有必要,因为具有函数(而不是类)的模块可以很好地用作单例。它的所有变量都将绑定到模块,无论如何都不能重复实例化。

如果您确实希望使用类,则无法在 Python 中创建私有类或私有构造函数,因此您无法防止多个实例化,只能通过使用 API 的约定。我仍然会将方法放在模块中,并将模块视为单例。

【讨论】:

构造函数不能只检查一个实例是否已经创建,如果已经创建则抛出异常? 这很好,只要您不需要在设计中使用继承,在这种情况下,下面的大多数答案都更合适 循环导入就坏了 如果我希望该模块是可继承的,我该怎么办? 我认为这是错误的。关于模块级接口的一个烦恼是管理导入。例如,Python logging 是一个模块级接口。为了确保您在logging 之后完全清理干净,您必须致电logging.shutdown()。这意味着您必须将logging 导入到调用shutdown 的模块中。如果它是单例模式,则可以在传递给它的任何模块中的实例上调用shutdown。【参考方案13】:

您可以像这样覆盖__new__ 方法:

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(
                                cls, *args, **kwargs)
        return cls._instance


if __name__ == '__main__':
    s1 = Singleton()
    s2 = Singleton()
    if (id(s1) == id(s2)):
        print "Same"
    else:
        print "Different"

【讨论】:

警告:如果 __new__() 返回 cls 的实例,则新实例的 __init__() 方法将像 __init__(self[, ...]) 一样被调用,其中 self 是新实例,其余参数与传递给 __new__() 的参数相同。如果 Singleton 的任何子类实现了 __init__(),它将被同一个 self 多次调用。我最终改用了工厂。 这里最好使用元类作为答案:***.com/a/33201/804147 这会给出以下警告 - singleton.py:9: DeprecationWarning: object.__new__() takes no parameters cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs) @Siddhant:更糟糕的是,在 Python 3 中,该警告变成了错误。请参阅bugs.python.org/issue1683368 和blog.jaraco.com/2014/05/… 了解更多详情。【参考方案14】:

正如accepted answer 所说,最惯用的方法就是使用模块

考虑到这一点,这里有一个概念证明:

def singleton(cls):
    obj = cls()
    # Always return the same object
    cls.__new__ = staticmethod(lambda cls: obj)
    # Disable __init__
    try:
        del cls.__init__
    except AttributeError:
        pass
    return cls

有关__new__ 的更多详细信息,请参阅Python data model。

例子:

@singleton
class Duck(object):
    pass

if Duck() is Duck():
    print "It works!"
else:
    print "It doesn't work!"

注意事项:

    为此,您必须使用新型类(派生自 object)。

    单例在定义时初始化,而不是在第一次使用时初始化。

    这只是一个玩具示例。我从未在生产代码中实际使用过它,也不打算这样做。

【讨论】:

我试过这个但是得到了错误:TypeError: unbound method () must be called with Integer instance as first argument (get type instance instead) 我的 Integer 类是你的 Duck 类:@singleton class Integer(object): """ 整数类型对象的类 """ pass 感谢您指出这一点。我不知道为什么会这样,但编辑后的版本应该可以在 Python 2.7 和 3.3 上运行。 这不好,__init__() 方法在定义类时被调用(而你可能想等到第一次使用它),然后在每次调用 Duck() . 我已经记录了第一个问题,并修复了第二个问题。感谢您指出。【参考方案15】:
class Singeltone(type):
    instances = dict()

    def __call__(cls, *args, **kwargs):
        if cls.__name__ not in Singeltone.instances:            
            Singeltone.instances[cls.__name__] = type.__call__(cls, *args, **kwargs)
        return Singeltone.instances[cls.__name__]


class Test(object):
    __metaclass__ = Singeltone


inst0 = Test()
inst1 = Test()
print(id(inst1) == id(inst0))

【讨论】:

【参考方案16】:

如果您想在未来装饰(注释)类,创建单例装饰器(也称为注释)是一种优雅的方式。然后,您只需将 @singleton 放在您的类定义之前。

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

@singleton
class MyClass:
    ...

【讨论】:

我想知道为什么这没有被投票?太好了.. 请解释为什么以及如何调用 getinstance 方法? 好像你复制了PEP318? @YugalJindle:仅供参考,简而言之,这里的类装饰器函数将传递给它的类对象替换为一个函数,该函数返回它通过调用它创建的类的新实例。第一次创建,如果不是第一次,则复制第一个。 这种方法的一个潜在问题(尽管可能很小)是类名最终会绑定到函数,而不是类对象。这意味着无法使用普通的 class Derived(MyClass) 语句创建 MyClass 的子类。 @tiho:我不同意这是一个主要问题,原因有几个。有些是:至少有几种方法很容易修复/解决方法,我认为创建类的主要原因是封装,不允许或支持继承,这在单例类中尤其如此。【参考方案17】:

我的简单解决方案是基于函数参数的默认值。

def getSystemContext(contextObjList=[]):
    if len( contextObjList ) == 0:
        contextObjList.append( Context() )
        pass
    return contextObjList[0]

class Context(object):
    # Anything you want here

【讨论】:

【参考方案18】:

Google 测试博客上也有一些有趣的文章,讨论为什么单例是/可能是坏的并且是一种反模式:

Singletons are Pathological Liars Where Have All the Singletons Gone? Root Cause of Singletons

【讨论】:

我把你的链接放在不同的行上,这样它们就不会全部合并成一个【参考方案19】:

模块方法效果很好。如果我绝对需要一个单例,我更喜欢 Metaclass 方法。

class Singleton(type):
    def __init__(cls, name, bases, dict):
        super(Singleton, cls).__init__(name, bases, dict)
        cls.instance = None 

    def __call__(cls,*args,**kw):
        if cls.instance is None:
            cls.instance = super(Singleton, cls).__call__(*args, **kw)
        return cls.instance

class MyClass(object):
    __metaclass__ = Singleton

【讨论】:

这种模式违反了“单一职责原则”(c2.com/cgi/wiki?SingleResponsibilityPrinciple)。请参阅blogs.msdn.com/scottdensmore/archive/2004/05/25/140827.aspx 中的第 (2) 点。 @haridsv 我不同意。类是单例的事实 在元类实现中被抽象出来——类本身不知道也不关心它是单例,因为它不负责执行该要求,元类是.但是,正如您所注意到的,下面的方法显然是一种违规行为。基类方法介于两者之间。 @dare2be:你提到的复制问题不能简单地通过让元类也向创建的类添加一个__deepcopy__() 方法来解决吗? @martineau:这是type.__init__,它是压倒一切的,而不是MyClass.__init__ 另一个 *** 评论提到你可以通过覆盖 new__() `` class SingletonMeta(type): def _new__(cls, name, bases, dict) 来修复这个错误: dict['_deepcopy'] = dict['copy'] = lambda self, *args: self return super(SingletonMeta, cls).__new__(cls, name,基础,字典)``` - ***.com/a/9887928/748503【参考方案20】:
class Singleton(object[,...]):

    staticVar1 = None
    staticVar2 = None

    def __init__(self):
        if self.__class__.staticVar1==None :
            # create class instance variable for instantiation of class
            # assign class instance variable values to class static variables
        else:
            # assign class static variable values to class instance variables

【讨论】:

这个解决方案很传奇,我很高兴我走过了这里【参考方案21】:

The Singleton Pattern implemented with Python 由 ActiveState 提供。

看起来诀窍是将应该只有一个实例的类放在另一个类中。

【讨论】:

以上是关于有没有一种简单、优雅的方式来定义单例? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

有没有一种简单、优雅的方式来定义单例? [复制]

有没有一种简单、优雅的方式来定义单例? [复制]

有没有更优雅的方式来“伪造”类继承?

是否有一种优雅的 Pythonic 方式来计算已处理的数据? [复制]

用JavaScript来实现单例模式

朝花夕拾设计模式之单例模式