Почему декораторы Python вместо закрытия?
Я все еще не оглядывался вокруг декораторов на Python.
Я уже начал использовать множество закрытий, чтобы делать такие вещи, как настройка функций и классов в моем кодировании.
Eg.
class Node :
def __init__(self,val,children) :
self.val = val
self.children = children
def makeRunner(f) :
def run(node) :
f(node)
for x in node.children :
run(x)
return run
tree=Node(1,[Node(2,[]),Node(3,[Node(4,[]),Node(5,[])])])
def pp(n) : print "%s," % n.val
printTree = makeRunner(pp)
printTree(tree)
Насколько я вижу, декораторы - это просто другой синтаксис для выполнения чего-то подобного.
Вместо
def pp(n) : print "%s," % n.val
printTree = makeRunner(pp)
Я бы написал:
@makeRunner
def printTree(n) : print "%s," % n.val
Это все, что нужно для декораторов? Или есть фундаментальное отличие, которое я пропустил?
Ответы
Ответ 1
Хотя это правда, что синтаксически декораторы являются просто "сахаром", это не лучший способ подумать о них.
Декораторы позволяют вам переплетать функциональность в ваш существующий код, не изменяя его. И они позволяют делать это так, чтобы они были декларативными.
Это позволяет использовать декораторы для аспектно-ориентированного программирования (АОП). Поэтому вы хотите использовать декоратор, когда у вас есть сквозная проблема, которую вы хотите инкапсулировать в одном месте.
Квинтенциальный пример, вероятно, будет вести журнал, где вы хотите зарегистрировать запись или выйти из функции, или и то, и другое. Использование декоратора эквивалентно применению рекомендаций (log this!) К точке соединения (во время ввода или выхода метода).
Украшение метода - это концепция, подобная ООП или понимание списков. Как вы указываете, это не всегда уместно, и может злоупотреблять. Но в нужном месте может быть полезно сделать код более модульным и развязанным.
Ответ 2
Являются ли ваши примеры реальным кодом или просто примерами?
Если это реальный код, я думаю, что вы злоупотребляете декораторами, возможно, из-за вашего фона (т.е. вы привыкли к другим языкам программирования)
Этап 1: избегать декораторов
def run(rootnode, func):
def _run(node): # recursive internal function
func(node)
for x in node.children:
_run(x) # recurse
_run(rootnode) # initial run
Этот метод run obesletes makeRunner. Ваш пример:
def pp(n): print "%s," % n.val
run(tree, pp)
Однако это игнорирует полностью генераторы, поэтому...
Этап 2: использование генераторов
class Node :
def __init__(self,val,children) :
self.val = val
self.children = children
def __iter__(self): # recursive
yield self
for child in self.children:
for item in child: # recurse
yield item
def run(rootnode, func):
for node in rootnode:
func(node)
Ваш пример остается
def pp(n): print "%s," % n.val
run(tree, pp)
Обратите внимание, что специальный метод __iter__
позволяет использовать конструкцию for node in rootnode:
. Если вам это не нравится, просто переименуйте метод __iter__
, например. walker
и измените цикл run
на: for node in rootnode.walker():
Очевидно, что функция run
может быть методом class Node
.
Как вы видите, я предлагаю вам напрямую использовать run(tree, func)
вместо привязки к имени printTree
, но вы можете использовать их в декораторе или использовать функцию functools.partial
:
printTree= functools.partial(run, func=pp)
и с этого момента вы просто
printTree(tree)
Ответ 3
Следуя ссылке голландского мастера AOP, вы обнаружите, что использование декораторов становится особенно полезным при запуске добавления параметров, чтобы изменить поведение декорированной функции/метода и прочитать, что выше определения функции намного проще.
В одном из проектов, о котором я помню, нам нужно было контролировать множество задач сельдерея, и поэтому мы придумали идею использования декоратора для плагинов и настройки, как это требуется, что-то вроде:
class tracked_with(object):
"""
Method decorator used to track the results of celery tasks.
"""
def __init__(self, model, unique=False, id_attr='results_id',
log_error=False, raise_error=False):
self.model = model
self.unique = unique
self.id_attr = id_attr
self.log_error = log_error
self.raise_error = raise_error
def __call__(self, fn):
def wrapped(*args, **kwargs):
# Unique passed by parameter has priority above the decorator def
unique = kwargs.get('unique', None)
if unique is not None:
self.unique = unique
if self.unique:
caller = args[0]
pending = self.model.objects.filter(
state=self.model.Running,
task_type=caller.__class__.__name__
)
if pending.exists():
raise AssertionError('Another {} task is already running'
''.format(caller.__class__.__name__))
results_id = kwargs.get(self.id_attr)
try:
result = fn(*args, **kwargs)
except Retry:
# Retry must always be raised to retry a task
raise
except Exception as e:
# Error, update stats, log/raise/return depending on values
if results_id:
self.model.update_stats(results_id, error=e)
if self.log_error:
logger.error(e)
if self.raise_error:
raise
else:
return e
else:
# No error, save results in refresh object and return
if results_id:
self.model.update_stats(results_id, **result)
return result
return wrapped
Затем мы просто украсили метод run
для задач с параметрами, необходимыми для каждого случая, например:
class SomeTask(Task):
@tracked_with(RefreshResults, unique=True, log_error=False)
def run(self, *args, **kwargs)...
Затем изменение поведения задачи (или вообще удаление трекинга) означало настройку одного параметра или комментирование украшенной строки. Супер легко реализовать, но что более важно, супер легко понять при проверке.
Ответ 4
Декораторы, в общем смысле, являются функциями или классами, которые обертывают вокруг другого объекта, которые расширяют или украшают объект. Декоратор поддерживает тот же интерфейс, что и обернутая функция или объект, поэтому получатель даже не знает, что объект был украшен.
A Закрытие - анонимная функция, которая ссылается на ее параметры или другие переменные за пределами ее области.
В основном, декораторы используют закрытия и не заменяют их.
def increment(x):
return x + 1
def double_increment(func):
def wrapper(x):
print 'decorator executed'
r = func(x) # --> func is saved in __closure__
y = r * 2
return r, y
return wrapper
@double_increment
def increment(x):
return x + 1
>>> increment(2)
decorator executed
(3, 6)
>>> increment.__closure__
(<cell at 0x02C7DC50: function object at 0x02C85DB0>,)
>>> increment.__closure__[0].cell_contents
<function increment at 0x02C85DB0>
Итак, декоратор сохраняет исходную функцию с закрытием.
В чем разница между закрытиями и декораторами в Python?