Разъяснение, почему декоратор вызывается только один раз - PullRequest
0 голосов
/ 14 марта 2019

Я не совсем понимаю этот код, полученный от здесь :

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

Почему wrapper_singleton.instance = None не устанавливает экземпляр равным none каждый раз, когда создается экземпляр класса? Я помещаю оператор print над этой строкой, и он вызывается только один раз. Спасибо

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

>>> id(first_one)
140094218762280

>>> id(another_one)
140094218762280

>>> first_one is another_one
True

1 Ответ

1 голос
/ 14 марта 2019

Почему wrapper_singleton.instance = None не устанавливает экземпляр равным none каждый раз, когда создается экземпляр класса?

Поскольку эта часть кода выполняется только тогда, когда класс оформлен.
Это:

@singleton
class TheOne:
    pass

функционально эквивалентно

class TheOne:
    pass

TheOne = singleton(TheOne)

Обе версии кода фактически возвращают функцию через магию functools.wraps, которая действует так, как если бы она былаобернутый, как прекрасно объясняет @ smarie здесь .

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 фактически назначено внутренней функции wrapper_singleton вашей singleton функции.
Следовательно, когда вы делаете TheOne(), вы не создаете экземпляр класса напрямуюВы вызываете wrapper_singleton, который делает это за вас.
Это означает, что функция singleton вызывается только когда вы украшаете класс или делаете это вручную через TheOne = singleton(TheOne).Он определяет wrapper_singleton, создает для него дополнительный атрибут instance (чтобы if not wrapper_singleton.instance не вызывал AttributeError), а затем возвращает его под именем TheOne.

.снова украсив класс.

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
...