2

可能重复:
是否有一种简单、优雅的方式在 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()

当你尝试它时,你会看到再次调用的__init__方法。Tracer让另一个实例引用原始实例不是有一个单例吗?我不想__init__再次运行该方法,因为它可能会覆盖以前的信息。也许单例是错误的或它的用途?

4

2 回答 2

6

我之前的回答无效,我已将其删除。但是,我发现了一个高度评价的 SO答案。主要区别在于它使用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子句中的定义导致SyntaxErrorPython 2 中的 a (即使块中的代码永远不会实际执行)。类似于本杰明彼得森的六个模块的with_metaclass()功能的解决方法,看起来像这样:

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

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

于 2012-12-09T23:33:02.997 回答
2

__init__被调用了两次,但在同一个对象上。你已经创建了一个单例,但 Python 不知道它是,所以它初始化每个被创建的对象。

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

记住:

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

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

于 2012-12-09T16:38:41.697 回答