Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
346 views
in Technique[技术] by (71.8m points)

python - 在Python中创建单例(Creating a singleton in Python)

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. (这个问题不是为了讨论是否需要单例设计模式 ,是否是反模式,还是针对任何宗教战争,而是要讨论如何以最pythonic的方式在Python中最好地实现此模式。) In this instance I define 'most pythonic' to mean that it follows the 'principle of least astonishment' . (在这种情况下,我定义“最pythonic”来表示它遵循“最少惊讶的原理” 。)

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. (当我可以简单地继承或修饰时,我不希望增加gumph来使几个类杂乱无章。)

Best methods: (最佳方法:)


Method 1: A decorator (方法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

Pros (优点)

  • Decorators are additive in a way that is often more intuitive than multiple inheritance. (装饰器的添加方式通常比多重继承更直观。)

Cons (缺点)

  • While objects created using MyClass() would be true singleton objects, MyClass itself is aa function, not a class, so you cannot call class methods from it. (使用MyClass()创建的对象将是真正的单例对象,而MyClass本身是一个函数,而不是类,因此您不能从中调用类方法。) Also for m = MyClass(); n = MyClass(); o = type(n)(); (同样对于m = MyClass(); n = MyClass(); o = type(n)();) m = MyClass(); n = MyClass(); o = type(n)(); then m == n && m != o && n != o (然后m == n && m != o && n != o)

Method 2: A base class (方法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

Pros (优点)

  • It's a true class (这是一个真正的课堂)

Cons (缺点)

  • Multiple inheritance - eugh! (多重继承-好!) __new__ could be overwritten during inheritance from a second base class? (__new__是否可以在从第二个基类继承时被覆盖?) One has to think more than is necessary. (人们必须思考的超出了必要。)

Method 3: A metaclass (方法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

Pros (优点)

  • It's a true class (这是一个真正的课堂)
  • Auto-magically covers inheritance (自动神奇地涵盖继承)
  • Uses __metaclass__ for its proper purpose (and made me aware of it) (将__metaclass__用于其适当的目的(并使我意识到这一点))

Cons (缺点)

  • Are there any? (有吗)

Method 4: decorator returning a class with the same name (方法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

Pros (优点)

  • It's a true class (这是一个真正的课堂)
  • Auto-magically covers inheritance (自动神奇地涵盖继承)

Cons (缺点)

  • 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 (_sealed属性的意义是什么)
  • Can't call methods of the same name on base classes using super() because they will recurse. (无法使用super()在基类上调用相同名称的方法,因为它们会递归。) This means you can't customize __new__ and can't subclass a class that needs you to call up to __init__ . (这意味着您不能自定义__new__ ,也不能将需要调用__init__类作为子类。)

Method 5: a module (方法5:一个模块)

a module file singleton.py (一个模块文件singleton.py)

Pros (优点)

  • Simple is better than complex (简单胜于复杂)

Cons (缺点)

  ask by theheadofabroom translate from so

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Use a Metaclass (使用元类)

I would recommend Method #2 , but you're better off using a metaclass than a base class. (我建议使用方法2 ,但最好使用元类而不是基类。) 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

Or in Python3 (或在Python3中)

class Logger(metaclass=Singleton):
    pass

If you want to run __init__ every time the class is called, add (如果要在每次调用该类时运行__init__ ,请添加)

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

to the if statement in Singleton.__call__ . (转到Singleton.__call__if语句Singleton.__call__ 。)

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) . (您可以在Python中找到带有type(obj)的对象的元类。) Normal new-style classes are of type type . (普通的新式类是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' . (上面代码中的Logger将是class 'your_module.Singleton' ,就像Logger的(唯一)实例是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. (当您使用Logger()调用logger时,Python首先询问Logger Singleton的元类该怎么做,从而允许实例创建被抢占??。) 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 . (此过程与Python在通过myclass.attribute引用类的一个属性时调用__getattr__来询问类的方法相同。)

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 struct s in Python using metaclasses. (参见例如http://code.activestate.com/recipes/498149/ ,它实质上是使用元类在Python中重新创建C样式的struct 。) The thread What are your (concrete) use-cases for metaclasses in Python? (线程Python中元类的(具体)用例是什么?) also provides some examples, they generally seem to be related to declarative programming, especially as used in ORMs. (还提供了一些示例,它们通常似乎与声明性编程有关,尤其是在ORM中使用的声明性编程。)

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. (在这种情况下,如果使用方法2 ,并且子类定义了__new__方法,则每次调用SubClassOfSingleton() 都会执行该方法-因为它负责调用返回存储实例的方法。) 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. (如果需要以其他方式自定义单例类定义,则使用元类可以提供更多控制权 。)

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. (您的单例不需要多重继承 (因为元类不是基类),但是对于使用多重继承的已创建类的子类 ,您需要确保单例类是第一个/最左边的一个具有重新定义的元类的类。 __call__这不太可能成为问题。) The instance dict is not in the instance's namespace so it won't accidentally overwrite it. (实例dict 不在实例的名称空间中,因此不会意外覆盖它。)

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 aa function, not a class, so you cannot call class methods from it." (正如您在“ MyClass本身是一个函数而不是一个类,因此您无法从中调用类方法”中指出的那样, 方法#1未能通过该测试。)

Python 2 and 3 Compatible Version (Python 2和3兼容版本)

Writing something that works in both Python2 and 3 requires using a slightly more complicated scheme. (编写适用于Python2和3的东西需要使用稍微复杂一些的方案。) 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. (由于元类通常是type type子类,因此可以在运行时使用它作为元类动态地创建一个中间基类,然后将用作公共Singleton基类的基类。) 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

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 . (一个可能的优点是,与纯元类不同, isinstance(inst, Singleton)将返回True 。)

Corrections (更正)

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. (_instances需要的类引用 ,则需要使用super()或你递归__new__实际上是你必须通过类的静态方法 ,而不是一个类的方法,作为实际的类还没有被创建时被调用。) 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()

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. (方法4比其他装饰器版本更好,但是它的代码比单例所需的代码更多,并且不清楚它的功能。)

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? (首先,让一个类成为几乎相同的类的子类,并且名称仅存在于其__class__属性中,这不是很奇怪吗?) 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. (这也意味着您无法定义任何使用super() 在其基类上调用相同名称的方法的方法,因为它们会递归。) This means your class can't customize __new__ , and can't derive from any classes that need __init__ called on them. (这意味着您的类无法自定义__new__ ,并且不能从需要对其调用__init__任何类派生。)

When to use the singleton pattern (何时使用单例模式)

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." (您在其中一项评论中说:“对我而言,伐木一直是Singletons的自然选择。”) You're absolutely right .<


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...