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

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

【问题讨论】:

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

标签: python design-patterns singleton


【解决方案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()
        

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

        【讨论】:

        【解决方案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() 不起作用,classmethods 不起作用等:@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 多次调用。我最终改用了工厂。
        • 这里最好使用元类作为答案:stackoverflow.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/issue1683368blog.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!"
        

        注意事项:

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

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

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

        【讨论】:

        • 我试过这个但是得到了错误: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 测试博客上也有一些有趣的文章,讨论为什么单例是/可能是坏的并且是一种反模式:

            【讨论】:

            • 我把你的链接放在不同的行上,这样它们就不会全部合并成一个
            【解决方案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__
            • 另一个 stackoverflow 评论提到你可以通过覆盖 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,基础,字典)``` - stackoverflow.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 提供。

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

            【讨论】:

              猜你喜欢
              • 2011-09-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-06-15
              • 1970-01-01
              相关资源
              最近更新 更多