在Python中创建单例模式

1655
这个问题并不讨论单例设计模式是否可取,是否是反模式或任何宗教战争,而是讨论如何以最符合Python风格的方式实现此模式。在这种情况下,我将“最符合Python风格”定义为遵循“最小惊奇原则”。
我有多个类需要成为单例(我的用例是一个记录器,但这不重要)。我不希望在几个类中添加额外的内容,而是可以简单地继承或装饰。
最佳方法:

方法1:装饰器

def singleton(class_):
    instances = {}
    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]
    return getinstance

@singleton
class MyClass(BaseClass):
    pass

优点

  • 装饰器的特性通常比多重继承更加直观。

缺点

  • While objects created using MyClass() would be true singleton objects, MyClass itself is a function, not a class, so you cannot call class methods from it. Also for

    x = MyClass();
    y = MyClass();
    t = type(n)();
    

那么x == y,但是x != t && y != t


方法二:基类
class Singleton(object):
    _instance = None
    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_, *args, **kwargs)
        return class_._instance

class MyClass(Singleton, BaseClass):
    pass

优点

  • 它是一个真正的类

缺点

  • 多重继承 - 呃!从第二个基类继承时可能会覆盖__new__?这需要比必要更多的思考。

方法三:元类

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

#Python2
class MyClass(BaseClass):
    __metaclass__ = Singleton

#Python3
class MyClass(BaseClass, metaclass=Singleton):
    pass

优点

  • 它是一个真正的类
  • 自动覆盖继承
  • 使用__metaclass__实现其正确的目的(并让我意识到了这一点)

缺点

  • 有吗?

方法四:返回同名类的装饰器


def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class_, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w,
                                    class_).__new__(class_,
                                                    *args,
                                                    **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(BaseClass):
    pass

优点

  • 它是一个真正的类
  • 自动地覆盖继承

缺点

  • 创建每个新类是否有开销?在这里,我们为每个要使其成为单例的类创建了两个类。虽然在我的情况下这很好,但我担心这可能不能扩展。当然,是否应该轻松扩展这种模式存在争议...
  • _sealed属性的意义是什么
  • 无法使用super()调用基类上同名的方法,因为它们会递归。这意味着您无法自定义__new__并且无法子类化需要您调用__init__的类。

方法五:一个模块

一个名为singleton.py的模块文件。

优点

  • 简单胜于复杂。

缺点

  • 不是延迟实例化的。

20
另外三种技术:使用模块替代(通常情况下,我认为这是Python中更适合的模式,但这也取决于你用它做什么);创建一个单一实例并直接操作它(foo.x 或者如果你坚持可以用 Foo.x 替代 Foo().x);使用类属性和静态/类方法(Foo.x)。 - Chris Morgan
23
如果你只打算使用类方法或静态方法,那么实际上就不需要创建一个类了。 - Cat Plus Plus
5
@猫: 效果类似,但创建全局变量的原因可能是任何事情,包括不知道更好的方法。为什么要创建一个单例?如果你不知道答案,那就不应该在这里。这种明确性不仅更符合Python的风格,而且使维护更加简单。是的,单例是全局变量的语法糖,但类也是一堆难看东西的语法糖,没有人会告诉你总是最好不用它们。 - theheadofabroom
35
反单例情绪是最糟糕的模拟船运动编程。对于那些听过(很少有人真正阅读过)“Goto语句被认为是有害的”并认为无论上下文如何,goto语句都是糟糕代码的标志的人也是如此。 - Hejazzman
5
你好,感谢你详细的帖子。我对模式编程和Python都比较新,而且我很惊讶尽管方法2似乎是最为人熟知的一种(它无处不在),但几乎没有人提到每次使用Singleton()或MyClass()时,尽管仅创建了一个对象,init()也会被调用。我没有尝试过,但据我所知,其他方法也是如此。 当实现单例模式时,这似乎并不理想,难道我漏掉了什么吗?当然,解决方案是设置属性以避免两次执行__init__。只是好奇。 - chrisvp
显示剩余30条评论
42个回答

2

Method: override __new__ after single use

class Singleton():
    def __init__(self):
        Singleton.instance = self
        Singleton.__new__ = lambda _: Singleton.instance

优点

  • 极其简单明了
  • 真正的类,不需要模块
  • 适当使用lambda和Pythonic猴子补丁技术

缺点

  • __new__可能会再次被覆盖

2

除了同意在模块级别全局变量上具有一般的Python建议之外,还有什么想法吗:

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class2, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w, class2).__new__(class2, *args, **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(object):
    def __init__(self, text):
        print text
    @classmethod
    def name(class_):
        print class_.__name__

x = MyClass(111)
x.name()
y = MyClass(222)
print id(x) == id(y)

输出结果为:

111     # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True    # this is actually the same instance

“_sealed” 属性有什么作用?就我所知,它好像没有任何作用?有些事情让我感到不安,似乎它的性能不应该那么差...我将在本周晚些时候运行一些比较测试。 - theheadofabroom
_sealed 确保你的 init 只运行一次;我不明白为什么它应该比常规函数装饰器表现更差 - 每个类只执行一次该函数,并返回一个新的继承类。 - Guard
顺便说一句,你的编辑包含了打破缩进的制表符。你还说“我们正在创建2个类” - 你是指我们正在创建“1个额外的类”吗? - Guard
@agf 你能解释一下为什么 super(MyClass, self).__init__(text) 会递归吗?据我理解,super(MyClass, self) 应该是 object - ovgolovin
有更多信息的名称会很好。我看不懂这个,w是什么?class_2是什么? - Gulzar
显示剩余6条评论

2
这个怎么样:
def singleton(cls):
    instance=cls()
    cls.__new__ = cls.__call__= lambda cls: instance
    cls.__init__ = lambda self: None
    return instance

将其用作应该是单例的类上的装饰器,就像这样:

@singleton
class MySingleton:
    #....

这与另一个答案中的 singleton = lambda c: c() 装饰器类似。与其他解决方案一样,唯一的实例具有类名 (MySingleton)。然而,使用此解决方案,您仍然可以通过执行 MySingleton() 从类中“创建”实例(实际上获取唯一实例)。它还通过执行 type(MySingleton)() (也返回相同的实例)来防止您创建额外的实例。


你没有定义一个类以使用它作为一个对象。 - 0xc0de
1
每次调用 type(MySingleton)() 时,MySingleton.__init__() 都会被调用并且对象会被多次初始化;你可以通过在你的 singleton 中编写 cls.__init__ = lambda self: pass 来修复它。此外,覆盖 cls.__call__ 似乎是毫无意义的,甚至有害 - 在这个上下文中定义的 __call__ 是在你调用 MySingleton(any, list, of, arguments) 时使用的,而不是在你调用 type(MySingleton)(any, list, of, arguments) 时使用的。 - GingerPlusPlus
@GingerPlusPlus,感谢您指出在执行type(MySingleton)()时会再次调用__init __()。您提出的解决方案(添加cls.__init__ = lambda self: pass)会导致语法错误,因为lambda表达式的最后一部分需要是一个表达式而不是一个语句。但是,添加cls.__init__ = lambda self: None就可以了,所以我将其添加到我的答案中。 - Tolli
1
@GingerPlusPlus,关于使用__call__。我的意图是使得type(MySingleton)()MySingleton()都返回实例。所以它正在做我想要的事情。你可以将MySingleton视为单例的类型或单例的实例(或两者兼而有之)。 - Tolli

2

我更喜欢使用装饰器语法而不是从元类中派生。我的看法是:

from typing import Callable, Dict, Set


def singleton(cls_: Callable) -> type:
    """ Implements a simple singleton decorator
    """
    class Singleton(cls_):  # type: ignore
        __instances: Dict[type, object] = {}
        __initialized: Set[type] = set()

        def __new__(cls, *args, **kwargs):
            if Singleton.__instances.get(cls) is None:
                Singleton.__instances[cls] = super().__new__(cls, *args, **kwargs)
            return Singleton.__instances[cls]

        def __init__(self, *args, **kwargs):
            if self.__class__ not in Singleton.__initialized:
                Singleton.__initialized.add(self.__class__)
                super().__init__(*args, **kwargs)

    return Singleton


@singleton
class MyClass(...):
    ...

这种装饰器相对于其他提供的装饰器具有一些优点:

  • isinstance(MyClass(), MyClass) 仍然有效(返回闭包中的函数而不是类将使isinstance失败)
  • propertyclassmethodstaticmethod仍能按预期工作
  • __init__()构造函数只执行一次
  • 您可以再次使用@singleton从您的装饰类继承(无用?)

缺点:

  • print(MyClass().__class__.__name__)将返回Singleton而不是MyClass。如果您仍然需要此功能,建议按上面建议的使用元类。

如果您需要基于构造函数参数的不同实例,则需要改进此解决方案(由siddhesh-suhas-sathe提供的解决方案提供了此功能)。

最后,正如其他人建议的那样,在Python中考虑使用模块。模块是对象。您甚至可以将它们传递给变量并将其注入其他类中。


2

优点

真正的类,自动覆盖继承,使用元类来实现它的正确目的(并提醒了我),

缺点

有吗?

这将成为序列化的问题。如果您尝试从文件(pickle)反序列化对象,则不会使用 __call__,因此它将创建新文件。您可以使用基类继承和 __new__ 来防止出现这种情况。


1
基于Tolli的回答的代码。
#decorator, modyfies new_cls
def _singleton(new_cls):
    instance = new_cls()                                              #2
    def new(cls):
        if isinstance(instance, cls):                                 #4
            return instance
        else:
            raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))
    new_cls.__new__  = new                                            #3
    new_cls.__init__ = lambda self: None                              #5
    return new_cls


#decorator, creates new class
def singleton(cls):
    new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1
    return _singleton(new_cls)


#metaclass
def meta_singleton(name, bases, attrs):
    new_cls = type(name, bases, attrs)                                #1
    return _singleton(new_cls)

解释:

  1. 创建一个新类,继承给定的 cls(如果有人想要例如 singleton(list),它不会修改 cls)。

  2. 创建实例。在覆盖 __new__ 之前,这很容易。

  3. 现在,当我们已经轻松创建了实例,使用刚才定义的方法覆盖 __new__
  4. 当调用者期望的是 instance 时,函数仅返回它,否则会引发 TypeError
    当有人试图从装饰的类继承时,条件未满足。

  5. 如果 __new__() 返回一个 cls 的实例,则将调用新实例的 __init__() 方法,如 __init__(self[, ...]),其中 self 是新实例,其余参数与传递给 __new__() 的相同。

    instance 已经初始化,因此该函数将 __init__ 替换为不执行任何操作的函数。

在网上看它工作


1

如果您想将instance用作属性,则可以使用metaclass。例如:

class SingletonMeta(type):
    def __init__(cls, *args, **kwargs):
        super().__init__(*args, **kwargs)
        cls._instance = None
        cls._locker = threading.Lock()

    @property
    def instance(self, *args, **kwargs):
        if self._instance is None:
            with self._locker:
                if self._instance is None:
                    self._instance = self(*args, **kwargs)
        return self._instance


class MyClass(metaclass=SingletonMeta):
    def __init__(self):
        # init here
        pass


# get the instance
my_class_instance = MyClass.instance

当我们两次调用MyClass时会发生什么?我在这里得到了两个不同的地址...它似乎没有避免新实例。 - jossefaz

1

我记不清我在哪里找到这个解决方案,但我认为从我这个非Python专家的角度来看,它是最“优雅”的:

class SomeSingleton(dict):
    __instance__ = None
    def __new__(cls, *args,**kwargs):
        if SomeSingleton.__instance__ is None:
            SomeSingleton.__instance__ = dict.__new__(cls)
        return SomeSingleton.__instance__

    def __init__(self):
        pass

    def some_func(self,arg):
        pass

为什么我喜欢这个?没有装饰器,没有元类,没有多重继承……如果你决定不再想让它成为一个单例,只需删除__new__方法即可。由于我是Python(和OOP)新手,我希望有人能告诉我为什么这种方法很糟糕?


3
为什么这是一个糟糕的方法?当你想创建另一个单例类时,你必须复制并粘贴 __new__。不要重复自己(Don't repeat yourself)。 - GingerPlusPlus
另外,为什么你的新函数接收 *args**kwargs,却什么都不做呢?将它们传递给 dict.__new__,像这样:dict.__new__(cls, *args, **kwargs) - GingerPlusPlus
每次调用类时,都会调用__init__方法。如果您的__init__方法实际上有所作为,您会注意到这个问题。每当您执行SomeSingleton()时,都会通过__init__方法重置单例的状态。 - Aran-Fey

1

我要强调几个注意事项:

  1. metaclass 方法:
  • 你不能从两个元类中继承。查看
  • 如果你正在使用工厂模式,那么所有单例类都不会起作用。
  • 如果你的父类已经继承了 ABC(即元类),则你不能继承单例元类。
  1. 装饰器方法:
  • 我正在使用一个函数作为工厂接口,它从 base.__subclasses__() 创建 T 实例。
  • 这将跳过子类初始化的单例装饰器。

1
一行代码(我并不骄傲,但它能完成工作):
import sys

class Myclass:
  def __init__(self):
     # do your stuff
      vars(sys.modules[__name__])[type(self).__name__] = lambda: self # singletonify

我已经编辑了答案以避免这个问题。 - polvoazul

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