在Python中创建一个单例

这个问题不是用来讨论单例设计模式是否合意,是反模式还是用于任何宗教战争,而是讨论如何在python中最好地实现这种模式。 在这种情况下,我将'最pythonic'定义为它遵循'最不惊讶的原则'。

我有多个类将成为单身(我的用例是一个记录器,但这不重要)。 当我可以简单地继承或装饰时,我不希望杂乱地加上几个带有gumph的类。

最佳方法:


方法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

优点

  • 装饰器的添加方式通常比多重继承更直观。
  • 缺点

  • 尽管使用MyClass()创建的对象将是真正的单例对象,但MyClass本身是一个函数,而不是一个类,因此您无法从中调用类方法。 另外对于m = MyClass(); n = MyClass(); o = type(n)(); m = MyClass(); n = MyClass(); o = type(n)(); 那么m == n && m != o && n != o

  • 方法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
    

    优点

  • 这是一个真正的阶级
  • 缺点

  • 多重继承 - eugh! 在继承第二个基类时, __new__可能被覆盖? 人们必须思考超过必要的。

  • 方法3:元类

    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:修饰器返回一个同名的类

    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__的类__new__子类。

  • 使用Metaclass

    我会推荐方法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)
    

    Singleton.__call__if语句。

    关于元类的几句话。 元类是一个类的类 ; 也就是说,一个类是它的元类的一个实例 。 您可以使用type(obj)找到Python中对象的元类。 普通的新式类是类型type 。 上面代码中的Logger的类型为class 'your_module.Singleton' ,就像Logger的(唯一)实例的类型为class 'your_module.Logger' 。 当你使用Logger()调用logger时,Python首先会询问LoggerSingleton的元类,该怎么做,从而允许实例创建被预先占用。 这个过程与Python通过调用__getattr__在通过执行myclass.attribute引用其中一个属性时要求某个类执行什么操作相同。

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

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

    一般来说,使用元类来实现单例是有意义的 。 单例是特殊的,因为它只创建一次 ,而元类是您自定义创建方式。 如果您需要以其他方式定制单例类定义,使用元类可以提供更多的控制

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

    你也会听说单身模式违反了“单一责任原则” - 每个班级应做一件事 。 这样,如果您需要更换另一个代码,您不必担心代码会搞乱一件事,因为它们是分开的和封装的。 元类实现通过了这个测试 。 元类负责强制执行模式 ,创建的类和子类不需要知道它们是单例方法#1未通过此测试,正如您在“MyClass本身是一个函数,而不是一个类,因此您无法从中调用类方法”中所记录的。

    Python 2和3兼容版本

    编写在Python2和Python3中都可用的东西需要使用稍微复杂的方案。 由于元类通常是类型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__类派生出来。

    何时使用单身模式

    你的用例是想要使用单例的更好的例子之一。 你在其中一条评论中说:“对我来说,伐木一直是单身人士的天然选择。” 你是对的

    当人们说单身人士不好时,最常见的原因是他们是隐式共享状态 。 虽然使用全局变量和顶级模块导入是显式共享状态,但是传递的其他对象通常是实例化的。 这是一个很好的观点, 除了两个例外

    第一个,也是在不同地方提到的,是单身人士是恒定的 。 使用全局常量,尤其是枚举,被广泛接受,并被认为是理智的,因为无论如何, 任何用户都不能将它们混淆为任何其他用户 。 对于一个不变的单身人士来说也是如此。

    第二个例外,即少提及的情况是相反的 - 当单例只是一个数据宿 ,而不是数据源(直接或间接)。 这就是为什么伐木者觉得自己是单身的“自然”使用。 由于各种用户不会以其他用户关心的方式更改记录器 ,因此并没有真正的共享状态 。 这抵消了针对单例模式的主要争论,并且使它们成为合理的选择,因为它们易于使用

    以下是http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html中的引文:

    现在,有一种单身可以。 这是一个单身,其中所有可到达的对象都是不可变的。 如果所有对象都是不可变的,那么Singleton没有全局状态,因为所有对象都是常量。 但把这种单身人士变成易变的单身人士是很容易的,这是非常滑的斜坡。 因此,我也反对这些单身人士,不是因为他们不好,而是因为他们很容易变坏。 (作为一个便笺,Java枚举就是这种单例,只要你没有把枚举放进你的枚举中,你就可以,所以请不要。)

    其他类型的Singletons是半可接受的,它们不影响代码的执行,它们没有“副作用”。 记录就是一个很好的例子。 它加载了单例和全局状态。 这是可以接受的(因为它不会伤害到你),因为不管给定的记录器是否启用,你的应用程序都不会有任何不同。 这里的信息以一种方式流动:从您的应用程序到记录器。 即使记录器是全局状态,因为没有信息从记录器流入您的应用程序,记录器是可以接受的。 如果你想让你的测试断言某些东西被记录下来,你仍然应该注入你的记录器,但是一般情况下,记录器尽管处于充满状态并不是有害的。


    class Foo(object):
         pass
    
    some_global_variable = Foo()
    

    模块只能导入一次,其他所有内容都会过时。 不要使用单例,并尽量不要使用全局变量。


    使用一个模块。 它只进口一次。 定义一些全局变量 - 它们将是单例的“属性”。 添加一些功能 - 单身人士的“方法”。

    链接地址: http://www.djcxy.com/p/4075.html

    上一篇: Creating a singleton in Python

    下一篇: How to leave/exit/deactivate a python virtualenv?