您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

在Python中创建单例

在Python中创建单例

使用元类

我建议使用方法2,但最好使用元类而不是基类。这是一个示例实现:

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]

class Logger(object):
    __Metaclass__ = Singleton

或在python3

class Logger(Metaclass=Singleton):
    pass

如果要在__init__每次调用类时运行,请添加

        else:
            cls._instances[cls].__init__(*args, **kwargs)

对中的if陈述Singleton.__call__

关于元类的几句话。元类是类的类 ; 也就是说,类是其元类的实例。你可以使用来找到Python中对象的元类type(obj)。普通的新式类是类型type。Logger上面代码中的将会是type class ‘your_module.Singleton’,就像的(唯一的)实例Logger将是type一样class ‘your_module.Logger’。当你调用记录仪与Logger(),Python的首先要求的元类Logger,Singleton,做什么,允许实例创建要捷足先登。此过程与Python在通过class __getattr__引用类的一个属性时通过调用类要求做什么一样myclass.attribute

元类从本质上决定了类定义的含义以及如何实现该定义。参见例如http://code.activestate.com/recipes/498149/,它实际上是struct使用元类在Python中重新创建C风格的。线程元类的一些(具体)用例是什么?还提供了一些示例,它们通常似乎与声明性编程有关,尤其是在ORM中使用。

在这种情况下,如果你使用方法2,并且子类定义一个__new__方法,则每次调用时都会执行SubClassOfSingleton()该方法-因为它负责调用返回存储实例的方法。对于元类,仅在创建唯一实例时才调用一次。你想自定义调用类的含义,该类由类的类型决定。

通常,使用元类实现单例是有意义的。单例很特别,因为它只能创建一次,而元类自定义类创建的方式。如果需要以其他方式自定义单例类定义,则使用元类可以为你提供更多控制。

你的单例不需要多重继承(因为元类不是基类),但是对于使用多重继承的已创建类的子类,你需要确保单例类是第一个/最左边的一个具有重新定义的元类的类。__call__这不太可能成为问题。实例dict 不在实例的名称空间中,因此不会意外覆盖它。

你还将听到单例模式违反了“单一责任原则”-每个类只能做一件事。这样,你无需担心如果需要更改另一代码,便会弄乱代码要做的一件事,因为它们是分开的和封装的。元类实现通过了此测试。元类负责执行模式,创建的类和子类不必知道它们是单例。正如你在“ MyClass本身是一个函数而不是一个类,因此你无法从中调用方法”中指出的那样,方法#1未能通过该测试。

Python 2和3兼容版本 编写适用于Python2和3的东西需要使用稍微复杂一些的方案。由于元类通常是type的子类type,因此可以在运行时使用它作为元类动态地创建一个中间基类,然后将其用作公共Singleton基类的基类。如下所示,解释起来比做起来难。

# works in Python 2 & 3
class _Singleton(type):
    """ A Metaclass that creates a Singleton base class when called. """
    _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]

class Singleton(_Singleton('SingletonMeta', (object,), {})): pass

class Logger(Singleton):
    pass

具有讽刺意味的是,此方法使用子类来实现元类一个可能的优点是,与纯元类不同,isinstance(inst, Singleton)它将返回True。

在另一个主题上,你可能已经注意到了这一点,但是原始文章中的基类实现是错误的。_instances需要在类上引用,你需要使用super()或递归,并且__new__实际上是一个静态方法,你必须将该类传递给,而不是类方法,因为尚未在其上创建实际的类叫做。所有这些事情对于元类实现也是正确的。

class Singleton(object):
  _instances = {}
  def __new__(class_, *args, **kwargs):
    if class_ not in class_._instances:
        class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
    return class_._instances[class_]

class MyClass(Singleton):
  pass

c = MyClass()

我本来是在写评论,但是太长了,因此我将在此处添加方法4比其他装饰器版本更好,但是它的代码比单例所需的代码更多,并且不清楚它的功能

主要问题源于该类是它自己的基类。首先,让一个类成为几乎相同的类的子类不是很奇怪__class__吗?这也意味着你不能定义调用同名的方法对它们的基类的任何方法super(),因为他们会重复。这意味着你的类无法自定义__new__,并且不能从需要对其__init__调用的任何类派生。

你的用例是想要使用单例的更好示例之一。你在其中一项评论中说:“对我而言,伐木一直是Singletons的自然选择。” 你说的对。

人们说单身人士不好时,最常见的原因是他们是隐性的共享状态。尽管全局变量和顶级模块导入是显式共享状态,但通常会实例化传递的其他对象。这是一个好点,但有两个例外。

一个,并且在各个地方都被提及的,是单例是恒定的。全局常数(尤其是枚举)的使用已被广泛接受,并且被认为是理智的,因为无论如何,任何用户都无法为其他任何用户弄乱它们。对于恒定的单例也同样如此。

第二个例外,与之相反,是相反的情况-当单例只是一个数据接收器,而不是数据源(直接或间接)时。这就是为什么记录器觉得单例的“自然”用法。由于各种用户没有以其他用户关心的方式更改记录器,因此并没有真正的共享状态。这消除了反对单例模式的主要观点,并使其成为合理的选择,因为它们易于执行任务。

这是来自http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html的报价:

现在,有一种Singleton可以。那是所有可达对象都是不可变的单例。如果所有对象都是不可变的,则Singleton没有全局状态,因为一切都是恒定的。但是将这种单身人士变成易变的人是如此容易,这是很滑的坡度。因此,我也反对这些Singleton,不是因为它们不好,而是因为它们很容易变坏。(作为一个附带说明,Java枚举就是这些单例。只要你不将状态放入枚举中就可以,所以请不要这样做。)

另一种半可接受的单例是那些不影响代码执行的单例,它们没有“副作用”。日志记录就是一个很好的例子。它加载了Singletons和全局状态。这是可以接受的(因为它不会对你造成伤害),因为无论是否启用给定的记录器,你的应用程序的行为都没有任何不同。此处的信息以一种方式流动:从你的应用程序进入记录器。甚至认为记录器是全局状态,因为没有信息从记录器流入你的应用程序,所以记录器是可以接受的。如果你想让测试断言某些东西正在被记录,那么你仍然应该注入记录器,但是总的来说,记录器即使处于满状态也不会有害。

python 2022/1/1 18:19:41 有582人围观

撰写回答


你尚未登录,登录后可以

和开发者交流问题的细节

关注并接收问题和回答的更新提醒

参与内容的编辑和改进,让解决方法与时俱进

请先登录

推荐问题


联系我
置顶