在 Python 中实现单例的最佳方法是什么

2024-11-25 08:50:00
admin
原创
156
摘要:问题描述:这个问题不是为了讨论单例设计模式是否可取、是否是反模式,也不是为了任何宗教战争,而是为了讨论如何以最符合 Python 风格的方式在 Python 中最好地实现这种模式。在这种情况下,我将“最符合 Python 风格”定义为它遵循“最小惊讶原则”。我有多个类将成为单例(我的用例是用于记录器,但这并不...

问题描述:

这个问题不是为了讨论单例设计模式是否可取、是否是反模式,也不是为了任何宗教战争,而是为了讨论如何以最符合 Python 风格的方式在 Python 中最好地实现这种模式。在这种情况下,我将“最符合 Python 风格”定义为它遵循“最小惊讶原则”

我有多个类将成为单例(我的用例是用于记录器,但这并不重要)。当我可以简单地继承或装饰时,我不希望用额外的麻烦弄乱几个类。

最佳方法:


方法 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它本身是一个函数,而不是一个类,因此您无法从中调用类方法。对于

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__从第二个基类继承时可能会被覆盖吗?我们必须多加考虑。


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


方法 5:模块

模块文件singleton.py

优点

  • 简单胜过复杂

缺点

  • 不是惰性实例化


解决方案 1:

使用元类

我推荐方法 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)。普通的新式类的类型为typeLogger上面代码中的 将是 类型class 'your_module.Singleton',就像 的(唯一)实例Logger将是 类型一样class 'your_module.Logger'。当您使用 调用 logger 时Logger(),Python 首先会询问 的元类要做Logger什么,从而允许抢先创建实例。此过程与当您通过执行 引用类的某个属性时,SingletonPython 通过调用 询问类要做什么相同。__getattr__`myclass.attribute`

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

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

一般来说,使用元类来实现单例是有意义的。单例之所以特殊,是因为它的实例只创建一次,而元类是自定义类创建的方式,允许它的行为与普通类不同。如果您需要以其他方式自定义单例类定义,使用元类可以让您拥有更多的控制权。

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

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

Python 2 和 3 兼容版本

编写在 Python2 和 Python 3 中都能正常工作的代码需要使用稍微复杂一些的方案。由于元类通常是类型的子类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的引文:

现在,有一种单例是可以的。即所有可访问对象都是不可变的单例。如果所有对象都是不可变的,那么单例就没有全局状态,因为一切都是恒定的。但是将这种单例变成可变的单例非常容易,这是一个非常危险的趋势。因此,我也反对这些单例,不是因为它们不好,而是因为它们很容易变坏。(顺便说一句,Java 枚举就是这种单例。只要你不把状态放入枚举中,你就没问题,所以请不要这么做。)

另一种半可接受的单例是那些不会影响代码执行的单例,它们没有“副作用”。日志记录就是一个完美的例子。它加载了单例和全局状态。它是可以接受的(因为它不会伤害你),因为无论是否启用了给定的记录器,你的应用程序的行为都不会有任何不同。这里的信息流向单向:从你的应用程序到记录器。即使记录器是全局状态,因为没有信息从记录器流入你的应用程序,记录器也是可以接受的。如果你想让你的测试断言某些东西正在被记录,你仍然应该注入你的记录器,但一般来说,尽管记录器充满了状态,但并没有什么害处。

解决方案 2:

class Foo(object):
     pass

some_global_variable = Foo()

模块仅导入一次,其他一切都是多虑了。不要使用单例,尽量不要使用全局变量。

解决方案 3:

使用模块。它仅被导入一次。在其中定义一些全局变量 - 它们将成为单例的“属性”。添加一些函数 - 单例的“方法”。

解决方案 4:

你可能永远不需要 Python 中的单例。只需在模块中定义所有数据和函数,你就拥有了一个事实上的单例:

import datetime
file_name=None

def set_file_name(new_file_name: str):
    global file_name
    file_name=new_file_name

def write(message: str):
    global file_name
    if file_name:
        with open(file_name, 'a+') as f:
            f.write("{} {}
".format(datetime.datetime.now(), message))
    else:
        print("LOG: {}", message)

使用方法:

import log
log.set_file_name("debug.log")
log.write("System starting")
...

如果你真的必须有一个单例类,那么我会选择:

class MySingleton(object):
    def foo(self):
        pass

my_singleton = MySingleton()

使用方法:

from mysingleton import my_singleton
my_singleton.foo()

mysingleton.py您的文件名在哪里MySingleton定义。这是可行的,因为第一次导入文件后,Python 不会重新执行代码。

解决方案 5:

你只需要一个装饰器,取决于 Python 版本:


Python 3.2+

执行

from functools import lru_cache

@lru_cache(maxsize=None)
class CustomClass(object):

    def __init__(self, arg):
        print(f"CustomClass initialised with {arg}")
        self.arg = arg

用法

c1 = CustomClass("foo")
c2 = CustomClass("foo")
c3 = CustomClass("bar")

print(c1 == c2)
print(c1 == c3)

输出

>>> CustomClass initialised with foo
>>> CustomClass initialised with bar
>>> True
>>> False

注意foo只打印了一次


Python 3.9+

执行

from functools import cache

@cache
class CustomClass(object):
    ...

解决方案 6:

以下是一句话:

singleton = lambda c: c()

使用方法如下:

@singleton
class wat(object):
    def __init__(self): self.x = 1
    def get_x(self): return self.x

assert wat.get_x() == 1

你的对象被急切地实例化。这可能是你想要的,也可能不是。

解决方案 7:

  • 如果想要拥有同一个类的多个实例,但前提是 args 或 kwargs 不同,则可以使用第三方 python 包Handy Decorators(包decorators)。

  • 前任。

1. 如果你有一个处理`serial`通信的类,并且要创建一个实例,你想将串行端口作为参数发送,那么使用传统方法是行不通的
2. 使用上面提到的装饰器,如果参数不同,则可以创建该类的多个实例。
3. 对于相同的参数,装饰器将返回已经创建的相同实例。
>>> from decorators import singleton
>>>
>>> @singleton
... class A:
...     def __init__(self, *args, **kwargs):
...         pass
...
>>>
>>> a = A(name='Siddhesh')
>>> b = A(name='Siddhesh', lname='Sathe')
>>> c = A(name='Siddhesh', lname='Sathe')
>>> a is b  # has to be different
False
>>> b is c  # has to be same
True
>>>

解决方案 8:

使用函数属性也非常简单

def f():
    if not hasattr(f, 'value'):
        setattr(f, 'value', singletonvalue)
    return f.value

解决方案 9:

我将推荐一种使用元类的优雅解决方案

class Singleton(type): 
    # Inherit from "type" in order to gain access to method __call__
    def __init__(self, *args, **kwargs):
        self.__instance = None # Create a variable to store the object reference
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            # if the object has not already been created
            self.__instance = super().__call__(*args, **kwargs) # Call the __init__ method of the subclass (Spam) and save the reference
            return self.__instance
        else:
            # if object (Spam) reference already exists; return it
            return self.__instance

class Spam(metaclass=Singleton):
    def __init__(self, x):
        print('Creating Spam')
        self.x = x


if __name__ == '__main__':
    spam = Spam(100)
    spam2 = Spam(200)

输出:

Creating Spam

从输出中可以看出,仅实例化了一个对象

解决方案 10:

我更喜欢这个解决方案,我发现它非常清晰明了。例如,它使用双重检查,看看其他线程是否已经创建了它。另外要考虑的是确保反序列化不会创建任何其他实例。https:
//gist.github.com/werediver/4396488

import threading


# Based on tornado.ioloop.IOLoop.instance() approach.
# See https://github.com/facebook/tornado
class SingletonMixin(object):
    __singleton_lock = threading.Lock()
    __singleton_instance = None

    @classmethod
    def instance(cls):
        if not cls.__singleton_instance:
            with cls.__singleton_lock:
                if not cls.__singleton_instance:
                    cls.__singleton_instance = cls()
        return cls.__singleton_instance


if __name__ == '__main__':
    class A(SingletonMixin):
        pass

    class B(SingletonMixin):
        pass

    a, a2 = A.instance(), A.instance()
    b, b2 = B.instance(), B.instance()

    assert a is a2
    assert b is b2
    assert a is not b

    print('a:  %s
a2: %s' % (a, a2))
    print('b:  %s
b2: %s' % (b, b2))

解决方案 11:

一行解决方案:

将其添加到任何初始化self.__class__.__new__ = lambda _: self

class WhateverClass():
    def __init__(self):
        self.__class__.__new__ = lambda _: self

优点

  • 非常简单明了。只需添加一行即可__init__

  • 真正的课程,无需模块

  • Python 式的 lambda、闭包和 monkey patching 用法

它是如何工作的?__new__在第一次调用之后被覆盖,__init__因此构造函数本质上是一次性的。lambda 用于self使用闭包返回引用,这使得一切都非常匿名。

解决方案 12:

这是我自己实现的单例。你所要做的就是装饰类;要获得单例,你必须使用Instance方法。以下是一个例子:

@Singleton
class Foo:
    def __init__(self):
        print 'Foo created'

f = Foo() # Error, this isn't how you get the instance of a singleton

f = Foo.Instance() # Good. Being explicit is in line with the Python Zen
g = Foo.Instance() # Returns already created instance

print f is g # True

代码如下:

class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Other than that, there are
    no restrictions that apply to the decorated class.
 
    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    Limitations: The decorated class cannot be inherited from.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

解决方案 13:

使用类变量(无装饰器)

通过重写__new__方法返回该类的相同实例。仅用于首次初始化类的布尔值:

class SingletonClass:
    _instance = None

    def __new__(cls, *args, **kwargs):
        # If no instance of class already exits
        if cls._instance is None:
            cls._instance = object.__new__(cls)
            cls._instance._initialized = False
        return cls._instance
        
    def __init__(self, *args, **kwargs):
        if self._initialized:
            return

        self.attr1 = args[0]
        # set the attribute to `True` to not initialize again
        self._initialized = True

解决方案 14:

from functools import cache

@cache
class xxx:
   ....

非常简单而且有效!

解决方案 15:

方法 3 看起来非常简洁,但是如果你想让你的程序在Python 2和Python 3中运行,它就行不通了。即使使用 Python 版本的测试保护单独的变体也会失败,因为 Python 3 版本在 Python 2 中会出现语法错误。

感谢 Mike Watkins:http ://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/ 。如果您希望程序在 Python 2 和 Python 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]

MC = Singleton('MC', (object), {})

class MyClass(MC):
    pass    # Code for the class implementation

我推测赋值中的“object”需要替换为“BaseClass”,但我还没有尝试过(我已经尝试过如图所示的代码)。

解决方案 16:

我会把我的扔进戒指里。这是一个简单的装饰品。

from abc import ABC

def singleton(real_cls):

    class SingletonFactory(ABC):

        instance = None

        def __new__(cls, *args, **kwargs):
            if not cls.instance:
                cls.instance = real_cls(*args, **kwargs)
            return cls.instance

    SingletonFactory.register(real_cls)
    return SingletonFactory

# Usage
@singleton
class YourClass:
    ...  # Your normal implementation, no special requirements.

我认为它比其他一些解决方案具有以下优势:

  • 它清晰而简洁(在我看来;D)。

  • 其操作是完全封装的。您无需更改 的实现YourClass。这包括无需为您的类使用元类(请注意,上面的元类位于工厂中,而不是“真实”类中)。

  • 它不依赖于任何东西。

  • 对于呼叫者来说它是透明的:

+ 调用者仍然只需导入`YourClass`,它看起来像一个类(因为它就是),并且它们可以正常使用它。无需让调用者适应工厂函数。
+ 实例化的`YourClass()`仍然是您所实现的真实实例`YourClass`,而不是任何类型的代理,因此不会产生副作用。
+ `isinstance(instance, YourClass)`并且类似的操作仍按预期工作(尽管这部分确实需要 abc 因此排除了 Python <2.6)。

我确实想到了一个缺点:真实类的类方法和静态方法不能通过隐藏它的工厂类透明地调用。我很少使用这个,所以我从来没有遇到过这种需要,但是通过在工厂上使用自定义元类来轻松纠正这个问题,该元类实现__getattr__()将所有属性访问委托给真实类。

我发现一个更有用的相关模式(我并不是说这些事情经常需要)是“唯一”模式,其中使用相同参数实例化类会导致返回相同的实例。即“每个参数一个单例”。上面的代码很好地适应了这一点,并且变得更加简洁:

def unique(real_cls):

    class UniqueFactory(ABC):

        @functools.lru_cache(None)  # Handy for 3.2+, but use any memoization decorator you like
        def __new__(cls, *args, **kwargs):
            return real_cls(*args, **kwargs)

    UniqueFactory.register(real_cls)
    return UniqueFactory

尽管如此,我确实同意这样的一般建议:如果你认为你需要其中一种东西,你真的应该停下来想一想,问问自己是否真的需要。99% 的时间,YAGNI。

解决方案 17:

也许我误解了单例模式,但我的解决方案非常简单实用(pythonic?)。此代码实现了两个目标

  1. 使实例Foo在任何地方(全局)均可访问。

  2. 只能存在一个实例Foo

这是代码。

#!/usr/bin/env python3

class Foo:
    me = None

    def __init__(self):
        if Foo.me != None:
            raise Exception('Instance of Foo still exists!')

        Foo.me = self


if __name__ == '__main__':
    Foo()
    Foo()

输出

Traceback (most recent call last):
  File "./x.py", line 15, in <module>
    Foo()
  File "./x.py", line 8, in __init__
    raise Exception('Instance of Foo still exists!')
Exception: Instance of Foo still exists!

解决方案 18:

好吧,除了同意关于模块级全局的一般 Pythonic 建议之外,还可以这样做:

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class2, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w, class2).__new__(class2, *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(object):
    def __init__(self, text):
        print text
    @classmethod
    def name(class_):
        print class_.__name__

x = MyClass(111)
x.name()
y = MyClass(222)
print id(x) == id(y)

输出为:

111     # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True    # this is actually the same instance

解决方案 19:

这个怎么样:

def singleton(cls):
    instance=cls()
    cls.__new__ = cls.__call__= lambda cls: instance
    cls.__init__ = lambda self: None
    return instance

将其用作应为单例的类的装饰器。像这样:

@singleton
class MySingleton:
    #....

这类似于singleton = lambda c: c()另一个答案中的装饰器。与其他解决方案一样,唯一的实例具有类的名称(MySingleton)。但是,使用此解决方案,您仍然可以通过执行从类中“创建”实例(实际上获取唯一的实例)MySingleton()。它还阻止您通过执行type(MySingleton)()(也返回相同的实例)创建其他实例。

解决方案 20:

这个答案可能不是您想要的。我想要一个单例,即只有该对象才具有其身份,以便进行比较。在我的例子中,它被用作Sentinel Value。答案很简单,创建任何对象,mything = object()并且根据 Python 的性质,只有该对象才具有其身份。

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration

解决方案 21:

优点

它是一个真正的类,自动神奇地覆盖继承,使用元类
来实现其正确的目的(并且让我意识到这一点)缺点

有吗?

这将是序列化的问题。如果您尝试从文件 (pickle) 反序列化对象,它将无法使用__call__,因此它将创建新文件,您可以使用基类继承来__new__防止这种情况。

解决方案 22:

metaclass如果您想用作属性,可以使用instance。例如;

class SingletonMeta(type):
    def __init__(cls, *args, **kwargs):
        super().__init__(*args, **kwargs)
        cls._instance = None
        cls._locker = threading.Lock()

    @property
    def instance(self, *args, **kwargs):
        if self._instance is None:
            with self._locker:
                if self._instance is None:
                    self._instance = self(*args, **kwargs)
        return self._instance


class MyClass(metaclass=SingletonMeta):
    def __init__(self):
        # init here
        pass


# get the instance
my_class_instance = MyClass.instance

解决方案 23:

我也更喜欢装饰器语法,而不是从元类派生。我的看法是:

from typing import Callable, Dict, Set


def singleton(cls_: Callable) -> type:
    """ Implements a simple singleton decorator
    """
    class Singleton(cls_):  # type: ignore
        __instances: Dict[type, object] = {}
        __initialized: Set[type] = set()

        def __new__(cls, *args, **kwargs):
            if Singleton.__instances.get(cls) is None:
                Singleton.__instances[cls] = super().__new__(cls, *args, **kwargs)
            return Singleton.__instances[cls]

        def __init__(self, *args, **kwargs):
            if self.__class__ not in Singleton.__initialized:
                Singleton.__initialized.add(self.__class__)
                super().__init__(*args, **kwargs)

    return Singleton


@singleton
class MyClass(...):
    ...

这比其他装饰器提供的一些好处如下:

  • isinstance(MyClass(), MyClass)仍然可以工作(从子句中返回一个函数而不是一个类将导致 isinstance 失败)

  • property并且仍将按预期classmethod工作staticmethod

  • __init__()构造函数仅执行一次

  • 您可以再次使用 @singleton 从装饰类继承(没用?)

缺点:

  • print(MyClass().__class__.__name__)将返回Singleton而不是MyClass。如果您仍然需要它,我建议使用上述建议的元类。

如果您需要基于构造函数参数的不同实例,则需要改进此解决方案(siddhesh-suhas-sathe提供的解决方案提供了这一点)。

最后,正如其他人所建议的,考虑使用 Python 中的模块。模块对象。您甚至可以将它们传递到变量中并将它们注入其他类中。

解决方案 24:

我只是偶然做了一个简单的并想分享它......

class MySingleton(object):
    def __init__(self, *, props={}):
        self.__dict__ = props

mything = MySingleton()
mything.test = 1
mything2 = MySingleton()
print(mything2.test)
mything2.test = 5
print(mything.test)

解决方案 25:

它与 fab 的答案略有相似,但并不完全相同。

单例模式不需要我们能够多次调用构造函数。由于单例应该只创建一次,那么它难道不应该看起来只创建了一次吗?“欺骗”构造函数可能会损害可读性。

所以我的建议是这样的:

class Elvis():
    def __init__(self):
        if hasattr(self.__class__, 'instance'):
            raise Exception()
        self.__class__.instance = self
        # initialisation code...

    @staticmethod
    def the():
        if hasattr(Elvis, 'instance'):
            return Elvis.instance
        return Elvis()

instance这并不排除用户代码使用构造函数或字段:

if Elvis() is King.instance:

... 如果您确信Elvis尚未创建,并且King已经创建*。

但它鼓励用户普遍使用该the方法:

Elvis.the().leave(Building.the())

...特别是(但不完全是)如果对于猫王和/或那座建筑的先前存在存在疑问的话。

为了完成此操作,您还可以覆盖__delattr__()以在尝试删除时引发异常instance,并覆盖__del__()以便它引发异常(除非我们知道程序正在结束......)


  • 这里有一些本体论问题:一个单例能成为另一个单例吗?对单例类进行子类化似乎充满了问题。即便如此,你也可能会问isinstance(),不是is


进一步改进


感谢那些帮助我发表评论和编辑的人,欢迎大家多多指教。虽然我使用的是 Jython,但它应该更通用,而且是线程安全的。

try:
    # This is jython-specific
    from synchronize import make_synchronized
except ImportError:
    # This should work across different python implementations
    def make_synchronized(func):
        import threading
        func.__lock__ = threading.Lock()
    
        def synced_func(*args, **kws):
            with func.__lock__:
                return func(*args, **kws)

        return synced_func

class Elvis(object): # NB must be subclass of object to use __new__
    instance = None

    @classmethod
    @make_synchronized
    def __new__(cls, *args, **kwargs):
        if cls.instance is not None:
            raise Exception()
        cls.instance = object.__new__(cls, *args, **kwargs)
        return cls.instance
    
    def __init__(self):
        pass
        # initialisation code...

    @classmethod
    @make_synchronized
    def the(cls):
        if cls.instance is not None:
            return cls.instance
        return cls()

注意事项:

  1. 如果你没有在 python2.x 中从 object 子类化,那么你将得到一个旧式类,它不使用__new__

  2. 装饰时__new__必须使用@classmethod进行装饰,否则__new__将成为未绑定的实例方法

  3. 这可能可以通过使用元类来改进,因为这将允许您创建the一个类级属性,可能将其重命名为instance

解决方案 26:

我不记得在哪里找到这个解决方案,但从我非 Python 专家的角度来看,我发现它是最“优雅”的:

class SomeSingleton(dict):
    __instance__ = None
    def __new__(cls, *args,**kwargs):
        if SomeSingleton.__instance__ is None:
            SomeSingleton.__instance__ = dict.__new__(cls)
        return SomeSingleton.__instance__

    def __init__(self):
        pass

    def some_func(self,arg):
        pass

我为什么喜欢这个?没有装饰器,没有元类,没有多重继承……如果你决定不再想让它成为单例,只需删除该__new__方法即可。由于我是 Python(以及 OOP)的新手,我希望有人能告诉我为什么这是一种糟糕的方法?

解决方案 27:

代码基于Tolli 的回答。

#decorator, modyfies new_cls
def _singleton(new_cls):
    instance = new_cls()                                              #2
    def new(cls):
        if isinstance(instance, cls):                                 #4
            return instance
        else:
            raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))
    new_cls.__new__  = new                                            #3
    new_cls.__init__ = lambda self: None                              #5
    return new_cls


#decorator, creates new class
def singleton(cls):
    new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1
    return _singleton(new_cls)


#metaclass
def meta_singleton(name, bases, attrs):
    new_cls = type(name, bases, attrs)                                #1
    return _singleton(new_cls)

解释:

  1. 创建新类,继承自给定类(例如,如果有人想要,cls

它就不会修改)cls`singleton(list)`

  1. 创建实例。在覆盖之前,__new__这非常简单。

  2. 现在,当我们轻松地创建实例时,__new__使用刚才定义的方法进行覆盖。

  3. 仅当函数instance符合调用者期望时,它才会返回,否则会引发TypeError

当有人试图从装饰类继承时,条件不满足。

  1. 如果__new__()返回的实例cls,那么新实例的__init__()方法将像一样被调用__init__(self[, ...]),其中 self 是新实例并且其余参数与传递给的相同__new__()

instance已经初始化,因此函数将替换__init__为不执行任何操作的函数。

在线查看其运行情况

解决方案 28:

如果您不需要对 Singleton 实例进行延迟初始化,那么以下内容应该很容易并且是线程安全的:

class A:
    instance = None
    # Methods and variables of the class/object A follow
A.instance = A()

这种方式A是在模块导入时初始化的单例。

解决方案 29:

经过一段时间的挣扎,我最终想出了下面的方法,这样配置对象在从单独的模块调用时只会被加载一次。元类允许将全局类实例存储在内置字典中,目前看来这是存储适当的程序全局变量的最简洁的方法。

import builtins

# -----------------------------------------------------------------------------
# So..... you would expect that a class would be "global" in scope, however
#   when different modules use this,
#   EACH ONE effectively has its own class namespace.  
#   In order to get around this, we use a metaclass to intercept
#   "new" and provide the "truly global metaclass instance" if it already exists

class MetaConfig(type):
    def __new__(cls, name, bases, dct):
        try:
            class_inst = builtins.CONFIG_singleton

        except AttributeError:
            class_inst = super().__new__(cls, name, bases, dct)
            builtins.CONFIG_singleton = class_inst
            class_inst.do_load()

        return class_inst

# -----------------------------------------------------------------------------

class Config(metaclass=MetaConfig):

    config_attr = None

    @classmethod
    def do_load(cls):
        ...<load-cfg-from-file>...

解决方案 30:

一句话(我并不自豪,但它确实起了作用):

import sys

class Myclass:
  def __init__(self):
     # do your stuff
      vars(sys.modules[__name__])[type(self).__name__] = lambda: self # singletonify
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用