为什么使用Python装饰程序而不是闭包?
我还没有掌握Python中的装饰程序 我已经开始使用很多闭包来做一些事情,比如在编码中自定义函数和类 例如 就我所见,decorator只是做类似事情的不同语法 而不是为什么使用Python装饰程序而不是闭包?,python,decorator,Python,Decorator,我还没有掌握Python中的装饰程序 我已经开始使用很多闭包来做一些事情,比如在编码中自定义函数和类 例如 就我所见,decorator只是做类似事情的不同语法 而不是 def pp(n) : print "%s," % n.val printTree = makeRunner(pp) 我会写: @makeRunner def printTree(n) : print "%s," % n.val 这就是装饰师的全部吗?还是我错过了一个根本性的区别 您的示例是真正的代码,还是只是示例 如果它们
def pp(n) : print "%s," % n.val
printTree = makeRunner(pp)
我会写:
@makeRunner
def printTree(n) : print "%s," % n.val
这就是装饰师的全部吗?还是我错过了一个根本性的区别 您的示例是真正的代码,还是只是示例 如果它们是真正的代码,我认为您过度使用装饰程序,可能是因为您的背景(即您习惯于其他编程语言) 第一阶段:避免装饰 此运行方法淘汰makeRunner。你的例子是:
def pp(n): print "%s," % n.val
run(tree, pp)
然而,这完全忽略了生成器,所以
第二阶段:使用发电机
你的榜样依然存在
def pp(n): print "%s," % n.val
run(tree, pp)
请注意,特殊方法\uuuu iter\uuu
允许我们在rootnode:构造中为节点使用。如果您不喜欢,只需将\uu iter\uuu
方法重命名为例如walker
,并将运行
循环更改为:for rootnode.walker()中的节点:
显然,run
函数可以是类节点的方法
如您所见,我建议您直接使用run(tree,func)
,而不是将它们绑定到名称printree
,但您可以在装饰器中使用它们,也可以使用functools。分部
函数:
printTree= functools.partial(run, func=pp)
从那时起,你会
printTree(tree)
诚然,从句法上讲,装饰师只是“糖”,但这并不是思考它们的最佳方式
装饰器允许您将功能编织到现有代码中,而无需实际修改它。它们允许你以一种声明性的方式来做
这允许您使用装饰器进行面向方面编程(AOP)。所以,当你有一个横切的关注点,你想要封装在一个地方时,你想要使用一个装饰器
最典型的例子可能是日志记录,您希望记录函数的入口或出口,或两者。使用decorator相当于向连接点(在方法进入或退出期间)应用建议(记录此!)
方法装饰是一个类似于OOP或列表理解的概念。正如您所指出的,它并不总是合适的,可能会被过度使用。但是在正确的地方,它可以使代码更加模块化和解耦。继Dutch Master的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)...
然后改变任务的行为(或者完全删除跟踪)意味着调整一个参数,或者注释掉修饰的行。非常容易实现,但更重要的是,检查时非常容易理解。装饰器一般来说是围绕另一个对象的函数或类,用于扩展或装饰对象。装饰器支持与包装的函数或对象相同的接口,因此接收者甚至不知道对象已装饰
闭包是一个匿名函数,它引用其范围之外的参数或其他变量
所以基本上,装饰器使用闭包,而不是替换它们
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>
def增量(x):
返回x+1
def双_增量(func):
def包装(x):
打印“decorator已执行”
r=func(x)#-->func保存在_闭包中__
y=r*2
返回r,y
返回包装器
@双倍增量
def增量(x):
返回x+1
>>>增量(2)
装修工
(3, 6)
>>>增量.\uuu闭包__
(,)
>>>增量.\uuuu闭包\uuuu[0]。单元格内容
因此,装饰程序使用闭包保存原始函数而使用迭代器通常很好,我发现您的重写更糟糕。“for item in child:#recurse”的作用非常不明显,即使有注释也是如此。最初的代码使用的只是函数,更明显,更小;尽管基于我过去做过的类似事情。谢谢你的选择。有趣。我以前肯定用过stage 1表单,但我想我从来没有用iter运行过tree。实际上,看看你的最后一点,为什么你更喜欢functools.partial而不是decorator版本?(即,每种方法的优缺点是什么?@interstar:我修正了我最后一点的措辞;我只是提供了一种装饰的替代方法。@James Antill:迭代器的另一个名称是否会使它更明显,如child.walk_subtree()中的项的?我建议使用迭代器,因为它使代码的其余部分比传递函数对象更容易,特别是如果你想在遍历树时保持上下文的话;虽然解释很好,很简洁
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
class SomeTask(Task):
@tracked_with(RefreshResults, unique=True, log_error=False)
def run(self, *args, **kwargs)...
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>