This question is not for the discussion of whether or not the 100 is desirable, is an anti-pattern, or for any religious wars, but to discuss how this pattern is best implemented in Python in such a way that is most pythonic. In this instance I define 'most pythonic' to mean that it follows the 'principle of least astonishment'.

我有多个类,它们将成为单例(我的用例是针对记录器的,但这并不重要).当我可以简单地继承或装饰时,我不希望用添加的gumph来扰乱几个类.

最佳方法:


方法一:装饰师

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

专业人士

  • 装饰符的添加方式通常比多重继承更直观.

缺点

  • 虽然使用MyClass()创建的对象将是真正的单例对象,但MyClass本身是一个函数,而不是一个类,因此您不能从它调用类方法.还可用于

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

然后是x == y但是x != t && y != t


方法2:基类

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__可以在从第二个基类继承的过程中被覆盖?人们必须想得更多,而不是必要的.

Method 3: A metaclass

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__用于其适当用途(并让我意识到)

缺点

  • 有吗?

方法4:decorator返回同名的类

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

专业人士

  • 这是一门真正的课
  • 自动神奇地覆盖了继承

缺点

  • 创建每一个新类都没有开销吗?这里我们 for each 类创建两个类,我们希望创建一个单例.虽然这在我的情况下很好,但我担心这可能无法扩展.当然,对于这种模式是否太容易扩展,存在争议...
  • _sealed属性的意义是什么
  • 不能使用super()在基类上调用同名方法,因为它们将递归.这意味着您不能自定义__new__,也不能子类化需要您调用高达__init__的类.

方法5:一个模块

模块文件singleton.py

专业人士

  • 简单总比复杂好

缺点

  • 不是懒洋洋地说

推荐答案

使用元类

我推荐Method #2,但是使用metaclass比使用基类要好.以下是一个示例实现:

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

或者用Python 3

class Logger(metaclass=Singleton):
    pass

如果希望每次调用该类时都运行__init__,请添加

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

Singleton.__call__年的if号声明.

关于元类的几句话.元类是class of a class;也就是说,一个类是instance of its metaclass.你可以在Python中找到一个对象的元类type(obj).普通的新式课程是type型的.上面代码中的Logger将是class 'your_module.Singleton'类型,就像Logger的(唯一)实例将是class 'your_module.Logger'类型一样.当您使用Logger()调用logger时,Python首先询问LoggerSingleton元类该做什么,从而允许先发制人地创建实例.这个过程与Python在通过myclass.attribute引用类的一个属性时,通过调用__getattr__询问类要做什么相同.

元类本质上决定what the definition of a class means以及如何实现该定义.参见示例http://code.activestate.com/recipes/498149/,它基本上使用元类在Python中重新创建了C风格的struct.线程What are some (concrete) use-cases for metaclasses?还提供了一些示例,它们通常似乎与声明式编程有关,尤其是在ORM中使用的.

在这种情况下,如果您使用Method #2,并且子类定义了__new__方法,那么您调用SubClassOfSingleton()的方法将是executed every time——因为它负责调用返回存储实例的方法.使用元类,当创建唯一的实例时,它将是only be called once.你想要customize what it means to call the class,这取决于它的类型.

一般来说,使用元类实现单例是非常困难的.单例是特殊的,因为它是created only once,而元类是定制creation of a class的方式.如果您需要以其他方式自定义单例类定义,使用元类将为您提供more control.

您的singleton won't need multiple inheritance(因为元类不是基类),但对于使用多重继承的subclasses of the created class,您需要确保singleton类是first / leftmost类,其中元类重新定义了__call__.这不太可能是一个问题.实例dict是not in the instance's namespace,所以它不会意外地覆盖它.

您还将听到,singleton模式违反了"单一责任原则"——每个类应该完成only one thing个任务.这样,如果需要更改另一个代码,就不必担心会弄乱代码所做的一件事,因为它们是独立的和封装的.元类实现passes this test.元类负责enforcing the pattern,而创建的类和子类不需要是aware that they are singletons.Method #1未能通过此测试,正如您在"MyClass本身是一个函数,而不是一个类,因此您无法从中调用类方法"中所指出的

Python 2 and 3 Compatible Version

编写既能在Python2中工作又能在Python3中工作的东西需要使用稍微复杂一点的方案.由于元类通常是type类型的子类,所以可以使用元类在运行时动态创建一个中间基类,并将其作为元类,然后使用that作为公共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需要是referenced on the class,你需要使用super()或者你是recursing,__new__实际上是一个静电方法,你必须到pass the class to,而不是一个类方法,因为当它被调用时,实际的类hasn't been created还没有被调用.所有这些都适用于元类实现.

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()

装饰者退还A类

我本来是在写 comments ,但是太长了,所以我在这里补充一下.Method #4比其他的decorator 版本要好,但是它的代码比单例所需的代码多,而且它的作用也不是很清楚.

主要的问题源于类本身的基类.首先,如果一个类是一个几乎相同的类的子类,而这个类的名称却只有__class__属性,这不是很奇怪吗?这也意味着不能用super()定义any methods that call the method of the same name on their base class,因为它们会递归.这意味着你的类不能自定义__new__,也不能从任何需要调用__init__的类派生.

何时使用单例模式

您的用例是one of the better examples个想要使用单例的用例.你在其中一条 comments 中说,"对我来说,日志(log)记录似乎一直是单身人士的自然 Select ."你已经absolutely right岁了.

当人们说单身不好时,最常见的原因是他们implicit shared state岁.虽然全局变量和顶级模块导入是explicit共享状态,但传递的其他对象通常是实例化的.这是一个很好的观点,with two exceptions.

第一个,也是在很多地方被提及的一个,是单身者constant岁的时候.使用全局常量,尤其是枚举,被广泛接受,并被认为是合理的,因为无论如何,none of the users can mess them up for any other user.对于一个恒定的单身汉来说也是如此.

第二个例外很少被提及,相反——当单例为only a data sink时,不是数据源(直接或间接).这就是为什么伐木工人觉得自己是单身人士的"天然"用途.由于不同的用户在其他用户关心的方面有not changing the loggers个,所以有not really shared state个.这否定了反对单例模式的主要论点,并使他们成为一个合理的 Select ,因为他们对这项任务的支持.

下面是http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html的一句话:

现在,有一种单身汉是可以的.这是一个单例,其中所有可到达的对象都是不可变的.如果所有对象都是不可变的,那么Singleton就没有全局状态,因为一切都是常量.但要把这种单态变成可变态是很容易的,这是一个非常危险的过程.因此,我也反对这些单身人士,不是因为他们不好,而是因为他们很容易变坏.(顺便说一句,Java枚举就是这种单例.只要你不在枚举中加入状态,你就可以了,所以请不要这样做.)

另一种半可接受的单例是那些不会影响代码执行的单例,它们没有"副作用".日志(log)记录就是一个很好的例子.它加载了单例和全局状态.这是可以接受的(因为它不会伤害您),因为无论是否启用了给定的记录器,您的应用程序的行为都没有任何不同.这里的信息是单向流动的:从您的应用程序到记录器.即使记录器是全局状态,因为没有信息从记录器流入您的应用程序,记录器也是可以接受的.如果您希望测试断言正在记录某些内容,则仍应注入记录器,但一般而言,尽管记录器充满状态,但不会造成伤害.

Python相关问答推荐

为什么我的代码会进入无限循环?

键盘.任务组

如何编写一个正规表达式来查找序列中具有2个或更多相同辅音的所有单词

Polars Dataframe:如何按组删除交替行?

Python:记录而不是在文件中写入询问在多文件项目中记录的最佳实践

是什么导致对Python脚本的jQuery Ajax调用引发500错误?

在上下文管理器中更改异常类型

Python中是否有方法从公共域检索搜索结果

优化在numpy数组中非零值周围创建缓冲区的函数的性能

Chatgpt API不断返回错误:404未能从API获取响应

如何在Deliveryter笔记本中从同步上下文正确地安排和等待Delivercio代码中的结果?

如何在python xsModel库中定义一个可选[December]字段,以产生受约束的SON模式

如何在solve()之后获得症状上的等式的值

Streamlit应用程序中的Plotly条形图中未正确显示Y轴刻度

DataFrames与NaN的条件乘法

关于Python异步编程的问题和使用await/await def关键字

在pandas中使用group_by,但有条件

matplotlib图中的复杂箭头形状

如何在Python请求中组合多个适配器?

在numpy数组中寻找楼梯状 struct