繁体   English   中英

我可以在Python中将两个装饰器组合为一个吗?

[英]Can I combine two decorators into a single one in 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

是。 请在此处查看装饰器的定义。

这样的事情应该起作用:

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_args1deco_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

在此示例中,您可以组成一个包含多个装饰器的新装饰器。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM