是否有一种简单,优雅的方式来定义单身人士?

Jam*_*mie 427 python singleton design-patterns

似乎有很多方法可以在Python中定义单例.Stack Overflow是否有共识?

Sta*_*ale 346

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

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

  • 只要您不需要使用继承作为设计的一部分,这很好,在这种情况下,下面的大部分答案都更合适 (38认同)
  • 如果我希望该模块可以继承,我该怎么办? (14认同)
  • 构造函数不能只检查是否已经创建了一个实例并抛出异常(如果已经存在)? (12认同)
  • 在我看来,这是错误的.关于模块级接口的一个烦恼是管理导入.例如,Python`logging`是一个模块级接口.为了确保在`logging`之后你能完全清理,你必须调用`logging.shutdown()`.这意味着您必须将`logging`导入到调用`shutdown`的模块中.如果它是单例模式,则可以在传递给它的任何模块中的实例上调用shutdown. (11认同)
  • 循环导入时它会被破坏 (10认同)
  • 试图用模块实现单例。我发现这根本行不通。模块可以通过多种方式导入两次,从而导致其丢失状态。 (2认同)
  • @martineau 不幸的是,这仅在理论上是正确的。存在允许模块导入两次的漏洞。例如,如果模块在不同命名空间下的pythonpath中出现两次,则可以导入两次,因为模块缓存系统无法告诉其同一个文件。 /sf/ask/335901261/​​ould-cause -a-python-module-to-be-imported-twice (2认同)

Pau*_*nta 289

这是我自己实现的单身人士.你所要做的就是装饰课程; 要获得单例,您必须使用该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
Run Code Online (Sandbox Code Playgroud)

这是代码:

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)
Run Code Online (Sandbox Code Playgroud)

  • 包含电池的Python应该是`desing_pattern`标准库的一部分,谢谢 (19认同)
  • 这是一个非常糟糕的***单例实现.首先,它不是一个合适的装饰器,因为它不使用[`functools.wraps`](https://docs.python.org/3/library/functools.html#functools.wraps)或[`functools. update_wrapper`](https://docs.python.org/3/library/functools.html#functools.update_wrapper).其次,必须通过调用`Foo.Instance()来获取实例是可怕的unpythonic,并且正好有0个理由为什么它不能被实现为`Foo()`.第三,替换像那样的类产生类似`type(Foo.instance())的意外结果是Foo` - >`False` (12认同)
  • @akhan我决定不支持带有参数的构造函数,因为参数只会在第一次使用而忽略所有其他时间.这可能会使您的代码很难遵循,因为您可能在不同的地方使用不同的参数,但您可能不知道这些调用中的哪一个实际初始化单例. (9认同)
  • @ Aran-Fey我看到Twitter话题已经进入StackOverflow。无论如何,通过调用`Foo()`获取实例不是一个好的API,因为它无法与您交流发生的事情。API不会一眼就告诉您您正在处理单例而​​不是普通类。 (6认同)
  • @akhan如果你真的想用参数初始化你的单例,你应该有一个单独的`initialize()`方法,如果多次调用,它可以接受任何参数和抛出. (5认同)
  • 此装饰器不支持带参数的构造函数.对于更通用的装饰器,使用:`def Instance(self,*args,**kwargs):self._instance = self._decorated(*args,**kwargs) (2认同)
  • 公平地说,我同意像“Foo.get_instance()”这样的东西比“Foo()”更具自记录性。但我说的另外两件事仍然成立,而且它仍然是一个**非常糟糕**的实现。 (2认同)
  • @ Aran-Fey似乎这种解决方案真的可以使您的泡沫大声笑。我不相信Paul Manta曾经说过这是全世界最好的解决方案。他只是想回答原始作者的问题。我认为这是解决python中“缺失”的好方法。 (2认同)

joj*_*ojo 183

您可以__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"
Run Code Online (Sandbox Code Playgroud)

  • 警告:如果__new __()返回cls的实例,则将调用新实例的__init __()方法,如__init __(self [,...]),其中self是新实例,其余参数与传递给__new __().如果Singleton的任何子类实现__init __(),它将使用相同的self多次调用.我最终使用了工厂. (59认同)
  • 这会更好地使用元类作为答案:http://stackoverflow.com/a/33201/804147 (6认同)
  • 这给出了以下警告 - "singleton.py:9:DeprecationWarning:object .__ new __()不带参数``cls._instance = super(Singleton,cls).__ new __(cls,*args,**kwargs)` (2认同)
  • @Siddhant:更糟糕的是,在Python 3中,该警告成为错误.有关详细信息,请参阅http://bugs.python.org/issue1683368和http://blog.jaraco.com/2014/05/how-to-safely-override-init-or-new-in.html. (2认同)

Pet*_*ann 110

在Python中实现单例的略微不同的方法是Alex Martelli(Google员工和Python天才)的borg模式.

class Borg:
    __shared_state = {}
    def __init__(self):
        self.__dict__ = self.__shared_state
Run Code Online (Sandbox Code Playgroud)

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

  • 也称为单稳态.可能比单身人士更邪恶. (82认同)
  • 是否有人能够解释为什么这不适用于新式课程? (25认同)
  • @JamesEmerton:我刚刚尝试使用Python 2.7.2,适用于新的样式类. (7认同)
  • @pylover:你是对的,它不是Singleton - 这可能是[Alex Martelli](http://stackoverflow.com/users/95810/alex-martelli)给它一个不同名字的部分原因 - 但是它的效果非常相似. (7认同)
  • 不适用于新样式类 (6认同)
  • instance1 == instance2得到False !! 这不是单身,但只是一个很好的技巧 (3认同)
  • 调用一个静态变量“博格模式”,瞧!你是蟒蛇天才。 (3认同)
  • 它实际上不是单例,但非常有用!+1! (2认同)

小智 81

模块方法运作良好.如果我绝对需要单身人士,我更喜欢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
Run Code Online (Sandbox Code Playgroud)

  • @haridsv我不同意.事实上,类是单元__is__在元类实现中被抽象出来 - 类本身并不知道或不关心它是单例,因为它不负责执行该要求,元类是.但是,正如您所注意到的,下面的方法显然是违规行为.基类方法介于两者之间. (16认同)
  • @martineau:那是'type .__ init__`它的重写,而不是'MyClass .__ init__` (3认同)
  • 这种模式违反了“单一职责原则”(http://c2.com/cgi/wiki?SingleResponsibilityPrinciple)。请参阅 http://blogs.msdn.com/scottdensmore/archive/2004/05/25/140827.aspx 中的第 (2) 点。 (2认同)
  • @dare2be:难道你提到的复制问题不能简单地通过让元类在创建的类中添加一个 `__deepcopy__()` 方法来解决吗? (2认同)
  • 另一个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, bases, dict) ``` - /sf/answers/692154991/ (2认同)

小智 43

PEP318看到这个实现,用装饰器实现单例模式:

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

@singleton
class MyClass:
    ...
Run Code Online (Sandbox Code Playgroud)

  • 这个装饰器的问题是'MyClass'不再是一个类,例如super()不起作用,classmethods不起作用等:@singleton类MyClass(BaseClass):def __init __(self):super(MyClass,个体经营).__的init __() (18认同)

Lam*_*iry 26

正如公认的答案所说,最惯用的方法是使用模块.

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

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
Run Code Online (Sandbox Code Playgroud)

有关更多详细信息,请参阅Python数据模型__new__.

例:

@singleton
class Duck(object):
    pass

if Duck() is Duck():
    print "It works!"
else:
    print "It doesn't work!"
Run Code Online (Sandbox Code Playgroud)

笔记:

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

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

  3. 这只是一个玩具的例子.我从来没有在生产代码中实际使用它,也不打算这样做.

  • 我已经记录了第一个问题,并修复了第二个问题.谢谢你指出来. (2认同)

u0b*_*6ae 20

我对此非常不确定,但我的项目使用'convention singletons'(非强制单例),也就是说,如果我有一个叫做的类DataController,我在同一个模块中定义它:

_data_controller = None
def GetDataController():
    global _data_controller
    if _data_controller is None:
        _data_controller = DataController()
    return _data_controller
Run Code Online (Sandbox Code Playgroud)

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


Bri*_*man 20

Python文档确实涉及到这一点:

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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

扩展它应该相对干净:

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

  • +1是唯一提到Guido van Rossum实施的人.然而你自己的版本是错误的:你不应该在`__new__`中使用`hasattr`和`getattr`,因为它们都调用`object .__ getattribute__`,它们通过所有类层次结构查找你的``__self __"`属性而不是只有当前的班级.如果Guido使用`__dict__`进行属性访问,那是有原因的.尝试:`A类(GuidoSingleton):pass`,`class B(A):pass`,`class C(YourSingleton):pass`,`class D(C):pass`,`print(A(),B (),C(),D())`.所有子类都引用了与`YourSingleton`相同的实例! (4认同)
  • +1 提醒我们 Python 文档始终是我们开始搜索单例模式和其他设计模式的最佳场所。 (2认同)

Dav*_*cke 15

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

class foo:
  x = 1

  @classmethod
  def increment(cls, y = 1):
    cls.x += y
Run Code Online (Sandbox Code Playgroud)

  • @martineau 我建议使用单例非常接近使用全局变量,无论它是如何实现的。 (2认同)
  • 单身人士在两方面比全局变量更好:他们根本不会污染全局命名空间(或者像你的答案一样多),并且他们也提供惰性评估,而全局变量通常不提供(而你的答案也没有) ). (2认同)

Mat*_*ock 10

如果要在未来装饰(注释)类,创建单例装饰器(也称为注释)是一种优雅的方式.然后你就把@singleton放在你的类定义之前.

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

@singleton
class MyClass:
    ...
Run Code Online (Sandbox Code Playgroud)

  • 这种方法的一个潜在 - 虽然可能很小 - 问题是类名最终会被绑定到一个函数,而不是一个类对象.这意味着使用普通的`类Derived(MyClass)`语句创建`MyClass`的子类是不可能的. (5认同)
  • 好像你复制了PEP318? (3认同)
  • @tiho:我不同意这是一个主要问题,原因有几个.有些是:至少在几种方面很容易修复/解决方法,我认为创建类的主要原因是封装,而不是允许或支持继承,尤其是单例类. (3认同)

Fra*_*nkS 9

谷歌测试博客上还有一些有趣的文章,讨论为什么单身人士/可能是坏的并且是一种反模式:


Mar*_*ans 5

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

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
Run Code Online (Sandbox Code Playgroud)

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

class NothingSpecial:
    pass

THE_ONE_AND_ONLY = NothingSpecial()
Run Code Online (Sandbox Code Playgroud)

这样,您可以针对没有副作用的新实例编写测试,并且不需要在模块中添加全局语句,如果需要,您可以在将来派生变体.