gpt4 book ai didi

python - 在 Python 中创建单例

转载 作者:行者123 更新时间:2023-12-01 16:13:06 31 4
gpt4 key购买 nike

这个问题不是讨论singleton design pattern是可取的,是一种反模式,或用于任何宗教 war ,但要讨论如何以最 pythonic 的方式在 Python 中最好地实现这种模式。在这种情况下,我定义“最pythonic”意味着它遵循“最小惊讶原则”。
我有多个类将成为单例(我的用例是记录器,但这并不重要)。当我可以简单地继承或装饰时,我不希望用添加的口香糖来混淆几个类。
最佳方法:

方法一:装饰器

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
    方法二:基类
    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__在从第二个基类继承期间可以覆盖吗?一个人必须想得比必要的多。

  • 方法三:A metaclass
    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__出于其正当目的(并让我意识到这一点)

  • 缺点
  • 有吗?

  • 方法四:装饰器返回同名类
    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__ .

  • 方法五:一个模块
    一个模块文件 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__每次调用类时,添加
            else:
    cls._instances[cls].__init__(*args, **kwargs)
    if声明于 Singleton.__call__ .
    关于元类的几句话。元类是 类(class) ;也就是说,一个类是一个 其元类的实例 .您可以使用 type(obj) 在 Python 中找到对象的元类.普通的新式类的类型是 type . Logger在上面的代码中,类型为 class 'your_module.Singleton' ,就像 Logger 的(唯一)实例一样类型为 class 'your_module.Logger' .当您使用 Logger() 调用记录器时,Python首先询问 Logger的元类, Singleton ,做什么,允许实例创建被抢占​​。这个过程与 Python 通过调用 __getattr__ 来询问类要做什么相同。当您通过执行 myclass.attribute 来引用它的属性之一时.
    元类本质上决定了 类的定义意味着什么以及如何实现该定义。参见例如 http://code.activestate.com/recipes/498149/ ,它基本上重新创建了 C 风格 struct s 在 Python 中使用元类。线程 What are some (concrete) use-cases for metaclasses?还提供了一些示例,它们通常似乎与声明式编程有关,尤其是在 ORM 中使用的。
    在这种情况下,如果您使用您的 方法#2 ,一个子类定义了一个 __new__方法,它将是 每次都执行 您拨打 SubClassOfSingleton() -- 因为它负责调用返回存储实例的方法。使用元类,它将 只被调用一次 ,当唯一的实例被创建时。您要 自定义调用类的含义 ,这是由它的类型决定的。
    一般来说,它 有道理使用元类来实现单例。单例很特别,因为它是 仅创建一次 ,而元类是您自定义 的方式。创建一个类 .使用元类为您提供 更多控制如果您需要以其他方式自定义单例类定义。
    您的单例 不需要多重继承 (因为元类不是基类),但对于 创建类的子类 使用多重继承,你需要确保单例类是 第一个/最左边一个具有重新定义的元类 __call__这不太可能成为问题。实例字典是 不在实例的命名空间中 所以它不会意外地覆盖它。
    你还会听到单例模式违反了“单一职责原则”——每个类都应该做 只有一件事 .这样,如果您需要更改另一件事,您就不必担心弄乱代码所做的一件事,因为它们是分开的和封装的。元类实现 通过此测试 .元类负责 强制执行模式 并且创建的类和子类不必是 意识到他们是单例 . 方法#1 未能通过此测试,正如您在“MyClass 本身是一个函数,而不是一个类,因此您不能从它调用类方法”中指出的那样。
    Python 2 和 3 兼容版本
    编写同时适用于 Python2 和 3 的东西需要使用稍微复杂的方案。由于元类通常是类型 type 的子类,可以使用 1 在运行时动态创建中间基类,并将其作为元类,然后将其用作公共(public)基类 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需要 在类(class)中引用 ,您需要使用 super()或者你是 递归 , 和 __new__实际上是一个静态方法,你必须 将类(class)传递给 ,不是类方法,作为实际类 尚未创建 然而当它被调用时。所有这些事情对于元类实现也是如此。
    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()因为它们会递归。这意味着您的类(class)无法自定义 __new__ ,并且不能从任何需要 __init__ 的类派生呼唤他们。
    何时使用单例模式
    您的用例是 更好的例子之一 想要使用单例。您在其中一条评论中说:“对我来说,日志记录似乎一直是单例的自然选择。”你是 绝对正确 .
    当人们说单例不好时,最常见的原因是他们是 隐式共享状态 .而全局变量和顶级模块导入是 显式 共享状态,其他传递的对象通常被实例化。这是个好点子, 除了两个异常(exception) .
    第一个,也是在各个地方都提到的,是当单例是 时。常数 .使用全局常量,尤其是枚举,被广泛接受,并被认为是理智的,因为无论如何, 没有一个用户可以为任何其他用户搞乱它们 .对于恒定的单例来说也是如此。
    较少提及的第二个异常(exception)情况正好相反——当单例为 时。只有一个数据接收器 ,不是数据源(直接或间接)。这就是为什么记录器感觉像是单例的“自然”用途。由于各个用户都是 不改变记录器 以其他用户会关心的方式,有 不是真正共享状态 .这否定了反对单例模式的主要论点,并使它们成为合理的选择,因为它们的 易用性 为任务。
    这里引用自 http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :

    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 - 在 Python 中创建单例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6760685/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com