Python "многоуровневый декоратор" - как это работает?


Я писал некоторые функции для операций с базами данных в скрипте и решил использовать декоратор функций для обработки шаблонных соединений с БД.

Урезанный пример показан ниже.

Декоратор функций:

import random

class funcdec(object):
    def __init__(self,func):
        self.state = random.random()
        self.func = func

    def __call__(self,*args,**kwargs):
        return self.func(self.state,*args,**kwargs)

@funcdec
def function1(state,arg1,**kwargs):
    print(state)

@funcdec
def function2(state,arg2,**kwargs):
    print(state)

function1(10)
function2(20)
Это означает, что я могу уменьшить количество шаблонной таблицы, но у меня есть другой объект состояния для каждой функции. Поэтому, если я запускаю это, я получаю что-то вроде:
python decf.py 
0.0513280070328
0.372581711374

Я хотел реализовать метод, позволяющий сделать это состояние общим для всех украшенные функции, и я придумал это.

Декорированная функция декоратора:

import random

class globaldec(object):
    def __init__(self,func):
        self.state = random.random()

    def __call__(self,func,*args,**kwargs):
        def wrapped(*args,**kawrgs):
            return func(self.state,*args,**kwargs)
        return wrapped

@globaldec
class funcdec(object):
    pass

@funcdec
def function1(state,arg1,**kwargs):
    print(state)

@funcdec
def function2(state,arg2,**kwargs):
    print(state)

function1(10)
function2(20)

Теперь, когда я выполняю это, объект состояния создается только один раз для каждого приложения, и состояние одинаково для всех декорированных функций, например:

python decg.py 
0.489779827086
0.489779827086
Интуитивно это имеет смысл для меня, потому что globaldec инициализируется только один раз для всех экземпляров функции decorator. Однако я немного туманно представляю себе, что именно здесь происходит, и тот факт, что объект funcdec похоже, что он больше не инициализируется и не вызывается.

Вопросы:

    Есть ли у этой техники название? Может ли кто-нибудь пролить еще немного света на то, что происходит внутри?
1 2

1 ответ:

Вы создали фабрику декораторов; вызываемый объект, который производит декоратора. В этом случае вы игнорируете Аргумент func к globaldec.__init__() (исходный объект класса funcdec) при использовании класса globaldec в качестве декоратора. Вместо этого вы заменили его экземпляром класса globaldec, который затем используется в качестве реального декоратора для function1 и function2.

Это потому, что декораторы-это просто синтаксический сахар; декоратор @globaldec, примененный к строке class funcdec:, может быть выражается так:

class funcdec(object):
    pass
funcdec = globaldec(funcdec)

Поэтому funcdec класс был заменен экземпляром globaldec вместо этого.

Вместо использования классов я бы использовал функции; состояния типа func и state становятся замыканиями.

Ваш оригинальный декоратор тогда может быть написан так:

import random

def funcdec(func):
    state = random.random()    
    def wrapper(*args, **kwargs):
        return func(state, *args, **kwargs)
    return wrapper

Поэтому, когда Python применяет это в качестве декоратора, funcdec() возвращает функцию wrapper, заменяя исходные функции function1 или function2 заменяются этим объектом функции. Вызывая wrapper() затем в свою очередь вызывает исходный объект функции через замыкание func.

Версия globaldec просто добавляет еще один слой; внешняя функция создает декоратор, перемещая закрытие на один шаг:

import random

def globaldec():
    state = random.random()    
    def funcdec(func):
        def wrapper(*args, **kwargs):
            return func(state, *args, **kwargs)
        return wrapper
    return funcdec

Просто создайте декоратора один раз:

funcdec = globaldec()

@funcdec
def function1(state,arg1,**kwargs):
    print(state)

@funcdec
def function2(state,arg2,**kwargs):
    print(state)

Альтернативным шаблоном было бы сохранение состояния как глобального (это можно сделать непосредственно в функции декоратора:

import random

def funcdec(func):
    if not hasattr(funcdec, 'state'):
        # an attribute on a global function is also 'global':
        funcdec.state = random.random()
    def wrapper(*args, **kwargs):
        return func(funcdec.state, *args, **kwargs)
    return wrapper

Теперь вам больше не нужно создавать специальный объект декоратора, wrapper теперь относится к funcdec.state как общая ценность.