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
как общая ценность.