澄清为什么装饰器只调用一次

Clarification on why decorator only called once

我对从 here 获得的这段代码感到困惑:

import functools

def singleton(cls):
    """Make a class a Singleton class (only one instance)"""
    @functools.wraps(cls)
    def wrapper_singleton(*args, **kwargs):
        if not wrapper_singleton.instance:
            wrapper_singleton.instance = cls(*args, **kwargs)
        return wrapper_singleton.instance
    print('****')
    wrapper_singleton.instance = None
    return wrapper_singleton

@singleton
class TheOne:
    pass

为什么每次实例化 class 时,wrapper_singleton.instance = None 不将实例设置为 none?我在这一行上面放了一个打印语句,它也只被调用一次。谢谢

>>> first_one = TheOne()
>>> another_one = TheOne()

>>> id(first_one)
140094218762280

>>> id(another_one)
140094218762280

>>> first_one is another_one
True

Why doesn't wrapper_singleton.instance = None set the instance to none each time the class is instantiated?

因为那部分代码只在 class 修饰时执行。
这个:

@singleton
class TheOne:
    pass

在功能上等同于

class TheOne:
    pass

TheOne = singleton(TheOne)

两个版本的代码实际上 return 通过 functools.wraps 的魔法实现的函数,它的行为就好像它是包装的可调用对象一样,如 @smarie excellently explains here.

TheOne = singleton(TheOne)
print(TheOne)
# <function TheOne at 0x00000000029C4400>

如果去掉@functools.wraps装饰,你对幕后有一个肤浅的了解:

def singleton(cls)
    #@functools.wraps(cls)
    def wrapper_singleton(*args, **kwargs): ...

TheOne = singleton(TheOne)
print(TheOne)
# <function singleton.<locals>.wrapper_singleton at 0x00000000029F4400>

因此名称 TheOne 实际上分配给了 singleton 函数的内部函数 wrapper_singleton
因此,当您执行 TheOne() 时,您不会直接实例化 class,而是调用 wrapper_singleton 来为您执行此操作。
这意味着,函数 singleton 仅在您装饰 class 或通过 TheOne = singleton(TheOne) 手动完成时调用。它定义了 wrapper_singleton,在其上创建了一个附加属性 instance(这样 if not wrapper_singleton.instance 就不会引发 AttributeError),然后 return 将其命名为 TheOne.

你可以通过再次装饰 class 来打破单例。

class TheOne:
    def __init__(self, arg):
        self.arg = arg

TheOne = singleton(TheOne)
t1 = TheOne(42)
print(t1.arg, id(t1))
# 42 43808640

# Since this time around TheOne already is wrapper_singleton, wrapped by functools.wraps,
# You have to access your class object through the __wrapped__ attribute
TheOne = singleton(TheOne.__wrapped__)
t2 = TheOne(21)
print(t2.arg, id(t2))
# 21 43808920