关于python:如何初始化一次singleton派生对象

How to initialize Singleton-derived object once

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
Is there a simple, elegant way to define Singletons in Python?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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__方法,因为它可能会覆盖以前的信息。也许单件是错的还是有用的?


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

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

将它应用到示例代码中可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

输出:

1
2
Init
a is b: True

更新

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

1
2
3
4
5
#!/usr/bin/env python3

class Tracer(object, metaclass=Singleton):
    def __init__(self):
        print("Init")

编写一个既适用于Python版本2又适用于Python版本3的代码是可能的,但是要复杂一点,因为您不能简单地有条件地这样定义它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
## 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的六个模块的with_metaclass()功能的解决方案如下:

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

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


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

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

记住:

  • 单元格是Java中的规范,但在Python中是不赞成的。

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