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 ответ:
Вы создали фабрику декораторов; вызываемый объект, который производит декоратора. В этом случае вы игнорируете Аргумент
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как общая ценность.