【问题标题】:How to initialize Singleton-derived object once [duplicate]如何初始化单例派生对象一次[重复]
【发布时间】:2012-11-27 04:11:46
【问题描述】:

可能重复:
Is there a simple, elegant way to define Singletons in Python?

我有以下示例代码,其中我从 Singleton 派生了一个类(希望是一个):

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = object.__new__(cls, *args, **kwargs)
        return cls._instance

class Tracer(Singleton):         
    def __init__(self):
        print "Init"

a = Tracer()
b = Tracer()

你试一试,你会看到Tracer__init__方法又被调用了。让另一个实例引用原始实例不是有一个单例吗?我不想再次运行__init__ 方法,因为它可能会覆盖以前的信息。也许单例是错误的或者它正在使用?

【问题讨论】:

  • 为什么需要单例?只需要一个模块级别的全局变量,您可以为它分配一个实例并用下划线命名该类,这样人们就知道不要实例化它。
  • 我更喜欢单例来依赖实现。我不想依赖“了解”任何人的惯例的人。
  • @Alex 任何尝试使用您的代码的人都不会认为它是一个单例,当他们尝试创建多个实例时会导致混乱。无论哪种方式,您都需要解释,我认为单身人士不太清楚,而且工作量更大。
  • 当然,您可能根本不需要课程。模块级常量可能就是您想要的。

标签: python singleton


【解决方案1】:

您的__init__ 被调用了两次,但在同一个对象上。您创建了一个单例,但 Python 不知道它是单例,因此它会初始化每个创建的对象。

如果你想追求单例模式,你必须将你的初始化代码移动到__new__,或者你的__new__调用的另一个方法中。

记住:

  1. 单例在 Java 中是常态,但在 Python 中却不受欢迎。

  2. 单例使您的代码更难测试,因为它们是从一个测试传递到下一个测试的全局状态。

【讨论】:

    【解决方案2】:

    我之前的回答无效,我已将其删除。 但是我找到了一个评价很高的 SO answer。主要区别在于它使用Singleton 元类 而不是基类,并重载其实例类的__call__() 方法而不是它们的__new__() 方法。这使它能够控制其单例类实例的实例的创建过程。可以定义一种额外的方法来删除其中的一个或多个——比如用于测试目的。

    另一个值得注意的实现细节是元类维护一个_instances 的字典,而不是只能保存一个值的字典。这允许它跟踪无限数量的单例实例(因为它可能是多个元类,因为它是可重用的)。

    将它应用到您的示例代码中会这样:

    class Singleton(type):
        """Metaclass."""
        _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 Tracer(object):
        __metaclass__ = Singleton
    
        def __init__(self):
            print("Init")
    
    a = Tracer()
    b = Tracer()
    print('a is b: {}'.format(a is b))  # same object? -> True
    

    输出:

    Init
    a is b: True
    

    更新

    指定元类的语法在 Python 2 和 3 之间有所不同。对于后者,您需要将 Tracer 类定义更改为:

    #!/usr/bin/env python3
    
    class Tracer(object, metaclass=Singleton):
        def __init__(self):
            print("Init")
    

    编写一个可以在 Python 版本 2 和 3 中运行的东西是可能的,但是会稍微复杂一些,因为您不能 简单地像这样有条件地定义它:

    ## Won't work ##
    
    if sys.version_info[0] < 3:  # Python 2?
        class Tracer(object):
            __metaclass__ = Singleton
    
            def __init__(self):
                print("Init")
    
    else:  # Python 3
        class Tracer(object, metaclass=Singleton):  # causes SyntaxError in Python 2
    
            def __init__(self):
                print("Init")
    

    因为else 子句中的定义导致 Python 2 中的 SyntaxError(即使块中的代码永远不会实际执行)。类似于 Benjamin Peterson 的 six 模块的 with_metaclass() 函数的解决方法,看起来像这样:

    class Tracer(Singleton("SingletonBaseClass", (object,), {})):
        def __init__(self):
            print("Init")
    

    这会动态创建一个继承所需元类的基类,从而避免由于两个 Python 版本之间的元类语法差异而导致的任何错误。 (它通过显式使用定义的元类来创建临时基类来实现。)

    【讨论】:

    • @eyquem:看看我的这个替代答案。
    • 你明白了!我没有时间研究我们在其他线程中找到的所有其他解决方案,然后将您的解决方案与其他解决方案进行比较。但它有效,我认为使用元类的能力值得一票。
    • @eyquem:再次感谢。我确实花了一些时间查看众多线程和冗长的答案,并选择了这个……还有许多其他人对链接答案中的答案(还有一些其他有用的信息)做了。很抱歉浪费您/我们的时间在我的另一个虚假回答上。
    猜你喜欢
    • 1970-01-01
    • 2018-03-01
    • 2012-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-25
    • 2020-11-18
    • 1970-01-01
    相关资源
    最近更新 更多