Python 使用装饰器的单例

Python Singleton using decorator

下面的装饰器如何使 Database 成为单例?

我理解的方式是 @singleton 装饰器使 Database class 成为 returns 一个 Database 对象的函数。然而,由于它现在像一个函数一样工作,当函数调用结束时,函数变量应该是死的(例如 instances 变量)。

奇怪的是,当我第二次调用这个函数时,instance变量似乎还活着并存储了以前的信息,即使id也是一样。这是怎么回事?有人可以帮我理解这个吗?

提前致谢

def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        nonlocal instances
        print(instances, id(instances))
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance

@singleton
class Database:
    def __init__(self):
        print('Loading database')

if __name__ == '__main__':
    db1 = Database() # first call.
    db2 = Database() # second call. why is this returning the same id?

输出

{} 4011784
Loading database
{<class '__main__.Database'>: <__main__.Database object at 0x0577C148>} 4011784

有几个概念共同使这成为可能。让我们看看您当前的代码:

@singleton
class Database:
    def __init__(self):
        print('Loading database')

上面的代码可以翻译成下面的代码。 @ 语法是以下形式的缩写:

class Database:
    def __init__(self):
        print('Loading database')

print(Database)
Database = singleton(Database) # manually decorated
print(Database)

class 声明本身存储到名为 Database 的变量中。之后,变量被替换为 singleton 函数调用(即 get_instance 函数)的结果。这意味着,每当您创建 Database 的新实例时,实际上现在会调用 get_instance

<class '__main__.Database'>
<function singleton.<locals>.get_instance at 0x7f8e82738310>

现在让我们来看看singleton函数:

def singleton(cls):
    instances = {}
    def get_instance(*args, **kwargs):
        nonlocal instances # nonlocal is not required here!
        print(instances, id(instances))
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance

singleton函数声明了一个属于singleton函数作用域的局部instances变量。每次调用 singleton 都会创建一个新的独立 get_instance 函数(所谓的闭包)。根据LEGB rule,封闭范围的变量是“inherit”(这在技术上是不正确的,但简化了解释)到闭包。这意味着,instances 将在每次调用特定 get_instance 函数时保持相同的对象。

下面我们来总结一下。由于每次调用 Database() 实际上 get_instance 被调用,并且由于 instances 每次调用这个特定的 get_instance 函数都保持不变,我们可以达到这样的效果,即class 只会创建一次,并且每次后续调用都会返回相同的对象。