在 Python 中创建单例 [英] Creating a singleton in Python

查看:37
本文介绍了在 Python 中创建单例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题不是讨论单例设计模式是可取的,是一种反模式,或用于任何宗教战争,但要讨论如何以最 Python 化的方式在 Python 中最好地实现这种模式.在这种情况下,我将最 Pythonic"定义为遵循最小惊讶原则".

This question is not for the discussion of whether or not the singleton design pattern 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'.

我有多个类会变成单例(我的用例是记录器,但这并不重要).当我可以简单地继承或装饰时,我不希望用添加的口香糖把几个类弄得杂乱无章.

I have multiple classes which would become singletons (my use-case is for a logger, but this is not important). I do not wish to clutter several classes with added gumph when I can simply inherit or decorate.

最佳方法:

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

then x == yx != 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__ 可以在从第二个基类继承的过程中被覆盖吗?一个人必须想得比必要的多.
  • Multiple inheritance - eugh! __new__ could be overwritten during inheritance from a second base class? One has to think more than is necessary.
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__ 用于其正确目的(并让我意识到这一点)
  • It's a true class
  • Auto-magically covers inheritance
  • Uses __metaclass__ for its proper purpose (and made me aware of it)

缺点

  • 有吗?
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__ 的类.
  • Is there not an overhead for creating each new class? Here we are creating two classes for each class we wish to make a singleton. While this is fine in my case, I worry that this might not scale. Of course there is a matter of debate as to whether it aught to be too easy to scale this pattern...
  • What is the point of the _sealed attribute
  • Can't call methods of the same name on base classes using super() because they will recurse. This means you can't customize __new__ and can't subclass a class that needs you to call up to __init__.

一个模块文件 singleton.py

优点

  • 简单胜于复杂

缺点

  • 未延迟实例化

推荐答案

使用元类

我会推荐方法#2,但最好使用元类而不是基类.这是一个示例实现:

Use a Metaclass

I would recommend Method #2, but you're better off using a metaclass than a base class. Here is a sample implementation:

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 中

Or in Python3

class Logger(metaclass=Singleton):
    pass

如果你想每次调用类都运行__init__,添加

If you want to run __init__ every time the class is called, add

        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首先询问Logger的元类,Singleton,要做什么,允许实例创建预先-清空.这个过程与 Python 通过调用 __getattr__ 来询问一个类要做什么相同,当你通过执行 myclass.attribute 引用它的一个属性时.

A few words about metaclasses. A metaclass is the class of a class; that is, a class is an instance of its metaclass. You find the metaclass of an object in Python with type(obj). Normal new-style classes are of type type. Logger in the code above will be of type class 'your_module.Singleton', just as the (only) instance of Logger will be of type class 'your_module.Logger'. When you call logger with Logger(), Python first asks the metaclass of Logger, Singleton, what to do, allowing instance creation to be pre-empted. This process is the same as Python asking a class what to do by calling __getattr__ when you reference one of it's attributes by doing myclass.attribute.

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

A metaclass essentially decides what the definition of a class means and how to implement that definition. See for example http://code.activestate.com/recipes/498149/, which essentially recreates C-style structs in Python using metaclasses. The thread What are some (concrete) use-cases for metaclasses? also provides some examples, they generally seem to be related to declarative programming, especially as used in ORMs.

在这种情况下,如果您使用方法#2,并且子类定义了一个__new__ 方法,它将每次都执行您调用 SubClassOfSingleton() —— 因为它负责调用返回存储实例的方法.对于元类,当创建唯一实例时,它只会被调用一次.您想自定义调用类的含义,这取决于它的类型.

In this situation, if you use your Method #2, and a subclass defines a __new__ method, it will be executed every time you call SubClassOfSingleton() -- because it is responsible for calling the method that returns the stored instance. With a metaclass, it will only be called once, when the only instance is created. You want to customize what it means to call the class, which is decided by it's type.

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

In general, it makes sense to use a metaclass to implement a singleton. A singleton is special because is created only once, and a metaclass is the way you customize the creation of a class. Using a metaclass gives you more control in case you need to customize the singleton class definitions in other ways.

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

Your singletons won't need multiple inheritance (because the metaclass is not a base class), but for subclasses of the created class that use multiple inheritance, you need to make sure the singleton class is the first / leftmost one with a metaclass that redefines __call__ This is very unlikely to be an issue. The instance dict is not in the instance's namespace so it won't accidentally overwrite it.

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

You will also hear that the singleton pattern violates the "Single Responsibility Principle" -- each class should do only one thing. That way you don't have to worry about messing up one thing the code does if you need to change another, because they are separate and encapsulated. The metaclass implementation passes this test. The metaclass is responsible for enforcing the pattern and the created class and subclasses need not be aware that they are singletons. Method #1 fails this test, as you noted with "MyClass itself is a a function, not a class, so you cannot call class methods from it."

编写同时适用于 Python2 和 3 的东西需要使用稍微复杂的方案.由于元类通常是 type 类型的子类,因此可以使用一个在运行时动态创建一个中间基类,并将其作为元类,然后使用 那个 作为基类公共 Singleton 基类.解释比做更难,如下图所示:

Writing something that works in both Python2 and 3 requires using a slightly more complicated scheme. Since metaclasses are usually subclasses of type type, it's possible to use one to dynamically create an intermediary base class at run time with it as its metaclass and then use that as the baseclass of the public Singleton base class. It's harder to explain than to do, as illustrated next:

# 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.

An ironic aspect of this approach is that it's using subclassing to implement a metaclass. One possible advantage is that, unlike with a pure metaclass, isinstance(inst, Singleton) will return True.

在另一个主题上,您可能已经注意到这一点,但是您原始帖子中的基类实现是错误的._instances 需要在类上引用,你需要使用 super() 或者你递归,并且 __new__ 实际上是一个静态方法,您必须将类传递给,而不是类方法,因为实际的类尚未创建 然而当它被调用时.所有这些都适用于元类实现.

On another topic, you've probably already noticed this, but the base class implementation in your original post is wrong. _instances needs to be referenced on the class, you need to use super() or you're recursing, and __new__ is actually a static method that you have to pass the class to, not a class method, as the actual class hasn't been created yet when it is called. All of these things will be true for a metaclass implementation as well.

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 比其他装饰器版本更好,但它比单例所需的代码多,而且不清楚它的作用.

Decorator Returning A Class

I originally was writing a comment but it was too long, so I'll add this here. Method #4 is better than the other decorator version, but it's more code than needed for a singleton, and it's not as clear what it does.

主要问题源于类是它自己的基类.首先,让一个类成为一个几乎相同的类的子类,而这个类的名称只存在于它的 __class__ 属性中,这不是很奇怪吗?这也意味着您不能使用 super() 定义任何在其基类上调用同名方法的方法,因为它们会递归.这意味着您的类不能自定义 __new__,也不能从任何需要对它们调用 __init__ 的类派生.

The main problems stem from the class being it's own base class. First, isn't it weird to have a class be a subclass of a nearly identical class with the same name that exists only in its __class__ attribute? This also means that you can't define any methods that call the method of the same name on their base class with super() because they will recurse. This means your class can't customize __new__, and can't derive from any classes that need __init__ called on them.

您的用例是想要使用单例的更好示例之一.您在其中一条评论中说对我而言,日志记录似乎一直是单身人士的自然候选者."你完全正确.

Your use case is one of the better examples of wanting to use a singleton. You say in one of the comments "To me logging has always seemed a natural candidate for Singletons." You're absolutely right.

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

When people say singletons are bad, the most common reason is they are implicit shared state. While with global variables and top-level module imports are explicit shared state, other objects that are passed around are generally instantiated. This is a good point, with two exceptions.

第一个,也是在不同地方被提及的,是单例常量.使用全局常量,尤其是枚举,被广泛接受,并被认为是合理的,因为无论如何,没有一个用户可以为任何其他用户搞砸.对于常量单例也是如此.

The first, and one that gets mentioned in various places, is when the singletons are constant. Use of global constants, especially enums, is widely accepted, and considered sane because no matter what, none of the users can mess them up for any other user. This is equally true for a constant singleton.

较少提及的第二个例外情况正好相反——当单例只是一个数据接收器,而不是一个数据源(直接或间接).这就是为什么伐木者感觉自己像一个自然"的人.用于单身人士.由于各种用户没有以其他用户会关心的方式更改记录器,因此没有真正共享状态.这否定了反对单例模式的主要论点,并使它们成为合理的选择,因为它们易于使用完成任务.

The second exception, which get mentioned less, is the opposite -- when the singleton is only a data sink, not a data source (directly or indirectly). This is why loggers feel like a "natural" use for singletons. As the various users are not changing the loggers in ways other users will care about, there is not really shared state. This negates the primary argument against the singleton pattern, and makes them a reasonable choice because of their ease of use for the task.

这里引用了 http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html:

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

Now, there is one kind of Singleton which is OK. That is a singleton where all of the reachable objects are immutable. If all objects are immutable than Singleton has no global state, as everything is constant. But it is so easy to turn this kind of singleton into mutable one, it is very slippery slope. Therefore, I am against these Singletons too, not because they are bad, but because it is very easy for them to go bad. (As a side note Java enumeration are just these kind of singletons. As long as you don't put state into your enumeration you are OK, so please don't.)

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

The other kind of Singletons, which are semi-acceptable are those which don't effect the execution of your code, They have no "side effects". Logging is perfect example. It is loaded with Singletons and global state. It is acceptable (as in it will not hurt you) because your application does not behave any different whether or not a given logger is enabled. The information here flows one way: From your application into the logger. Even thought loggers are global state since no information flows from loggers into your application, loggers are acceptable. You should still inject your logger if you want your test to assert that something is getting logged, but in general Loggers are not harmful despite being full of state.

这篇关于在 Python 中创建单例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆