【发布时间】:2011-07-21 12:50:03
【问题描述】:
有没有办法在 python 中将两个装饰器组合成一个新的装饰器?
我意识到我可以将多个装饰器应用于一个函数,但我很好奇是否有一些简单的方法可以将两个装饰器组合成一个新的。
【问题讨论】:
有没有办法在 python 中将两个装饰器组合成一个新的装饰器?
我意识到我可以将多个装饰器应用于一个函数,但我很好奇是否有一些简单的方法可以将两个装饰器组合成一个新的。
【问题讨论】:
有点笼统:
def composed(*decs):
def deco(f):
for dec in reversed(decs):
f = dec(f)
return f
return deco
然后
@composed(dec1, dec2)
def some(f):
pass
等价于
@dec1
@dec2
def some(f):
pass
【讨论】:
return lambda x: reduce(lambda y, f: f(y), decs, x)... 好吧,输入此代码后,我看到了您的代码的优势:)
@composed(dec1, dec2) 将等同于 @dec2 @dec1,这至少是违反直觉的。
@new_decorator = composed(dec1(some_params), dec2) 我用它来将装饰器逻辑放在一个地方(它用于 django 视图的用户授权)。您的代码将成为工具箱的有用补充。干杯。
dec2然后dec1,即dec1(dec2(some(f)))。 dec2 首先被执行。
是的。请参阅装饰器的定义,here。
这样的事情应该可以工作:
def multiple_decorators(func):
return decorator1(decorator2(func))
@multiple_decorators
def foo(): pass
【讨论】:
装饰器只是将函数作为输入并返回新函数的函数。这个:
@deco
def foo():
...
相当于这个:
def foo():
...
foo = deco(foo)
换句话说,装饰函数(foo)作为参数传递给装饰器,然后将foo替换为装饰器的返回值。有了这些知识,编写一个结合其他两个装饰器的装饰器就很容易了:
def merged_decorator(func):
return decorator2(decorator1(func))
# now both of these function definitions are equivalent:
@decorator2
@decorator1
def foo():
...
@merged_decorator
def foo():
...
如果装饰器接受参数,就有点棘手了,比如这两个:
@deco_with_args2(bar='bar')
@deco_with_args1('baz')
def foo():
...
您可能想知道这些装饰器是如何实现的。其实很简单:deco_with_args1 和deco_with_args2 是返回另一个 函数装饰器的函数。带参数的装饰器本质上是装饰器工厂。相当于这个:
@deco_with_args('baz')
def foo():
...
这是:
def foo():
...
real_decorator = deco_with_args('baz')
foo = real_decorator(foo)
为了制作一个接受参数然后应用其他两个装饰器的装饰器,我们必须实现自己的装饰器工厂:
def merged_decorator_with_args(bar, baz):
# pass the arguments to the decorator factories and
# obtain the actual decorators
deco2 = deco_with_args2(bar=bar)
deco1 = deco_with_args1(baz)
# create a function decorator that applies the two
# decorators we just created
def real_decorator(func):
return deco2(deco1(func))
return real_decorator
这个装饰器可以像这样使用:
@merged_decorator_with_args('bar', 'baz')
def foo():
...
【讨论】:
如果装饰器不接受额外的参数,你可以使用
def compose(f, g):
return lambda x: f(g(x))
combined_decorator = compose(decorator1, decorator2)
现在
@combined_decorator
def f():
pass
将等同于
@decorator1
@decorator2
def f():
pass
【讨论】:
如果你不想在测试套件中重复太多,你可以这样做::
def apply_patches(func):
@functools.wraps(func)
@mock.patch('foo.settings.USE_FAKE_CONNECTION', False)
@mock.patch('foo.settings.DATABASE_URI', 'li://foo')
@mock.patch('foo.connection.api.Session.post', autospec=True)
def _(*args, **kwargs):
return func(*args, **kwargs)
return _
现在您可以在测试套件中使用它,而不是在每个函数上方使用大量的装饰器::
def ChuckNorrisCase(unittest.TestCase):
@apply_patches
def test_chuck_pwns_none(self):
self.assertTrue(None)
【讨论】:
并扩展@Jochen 的答案:
import click
def composed(*decs):
def deco(f):
for dec in reversed(decs):
f = dec(f)
return f
return deco
def click_multi(func):
return composed(
click.option('--xxx', is_flag=True, help='Some X help'),
click.option('--zzz', is_flag=True, help='Some Z help')
)(func)
@click_multi
def some_command(**args):
pass
在此示例中,您可以编写一个包含多个装饰器的新装饰器。
【讨论】: