首页 > 后端开发 > Python教程 > 在 Python 中实现单例的最佳方式是什么?

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

Susan Sarandon
发布: 2024-12-17 16:07:09
原创
296 人浏览过

What is the best way to implement a singleton in 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 本身是一个函数,而不是一个类,因此无法调用类方法。

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

优点:

  • 简单胜于复杂。

缺点:

  • 未延迟实例化。

推荐方法

我建议使用 方法 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__,请将以下代码添加到 Singleton.__call__ 中的 if 语句中:

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
登录后复制
登录后复制
登录后复制

元类的作用

元类是类的类,也就是说,类是其元类的实例。可以通过 type(obj) 找到 Python 中对象的元类。正常的新的类是 type 类型。上面的 Logger 将属于 class 'your_module.Singleton' 类型,就像 Logger 的(唯一)实例将属于 class 'your_module.Logger' 类型一样。当使用 Logger() 调用 logger 时,Python 首先询问 Logger 的元类 Singleton 应该做什么,允许抢先进行实例创建。这个过程与 Python 通过调用 __getattr__ 询问类应该做什么来处理其属性类似,而你通过执行 myclass.attribute 引用其属性。

元类本质上决定了调用类的含义以及如何实现该含义。请参阅例如 http://code.activestate.com/recipes/498149/,它使用元类主要在 Python 中重新创建 C 样式结构。讨论线程 [元类的具体用例有哪些?](https://codereview.stackexchange.com/questions/82786/what-are-some-concrete-use-cases-for-metaclasses) 也提供了一些示例,它们通常与声明性编程有关,尤其是在 ORM 中使用。

在这种情况下,如果你使用你的 方法 2,并且一个子类定义了一个 __new__ 方法,它将在每次调用 SubClassOfSingleton() 时执行,因为它负责调用返回存储的实例的方法。使用元类,它只会执行一次,即在创建唯一实例时。你需要自定义调用类的定义,这是由其类型决定的。

一般来说,使用元类来实现单例是有意义的。单例很特别,因为它的实例只创建一次,而元类是自定义创建类的实现方式,使其行为与普通类不同。使用元类可以让你在其他方式需要自定义单例类定义时有更多的控制权。

当然

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

你还会听到单例模式违反了"单一职责原则",即每个类都应该只做一件事。这样,你不必担心在需要更改另一种代码时破坏代码所做的某一件事,因为它们是独立且封装的。元类实现通过了此测试。元类负责强制模式,创建的类和子类不需要意识到它们是单例。方法 1 没有通过此测试,正如你用"MyClass 本身是一个函数,而不是类,因此无法调用类方法"指出的那样。

Python 2 和 3 兼容版本

在 Python2 和 3 中编写代码需要使用稍微复杂一点的方案。由于元类通常是 type 类的子类,因此可以使用一个元类在运行时动态创建一个带有它作为元类的中介基类,然后使用该基类作为公共单例基类的基类。这说起来比做起来难,如下所示:

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
登录后复制
登录后复制
登录后复制

这种方法的一个讽刺之处在于它使用子类化来实现元类。一个可能的优点是,与纯元类不同的是,isinstance(inst, Singleton) 将返回 True。

修正

关于另一个话题,你可能已经注意到了,但你原始帖子中的基类实现是错误的。需要在类中引用 _instances,你需要使用 super(),或者是类方法的静态方法,因为在调用时实际类尚未创建。所有这些对于元类实现也是适用的。

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
登录后复制
登录后复制

以上是在 Python 中实现单例的最佳方式是什么?的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:php.cn
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板