【问题标题】:Can I combine two decorators into a single one in Python?我可以在 Python 中将两个装饰器组合成一个吗?
【发布时间】:2011-07-21 12:50:03
【问题描述】:

有没有办法在 python 中将两个装饰器组合成一个新的装饰器?

我意识到我可以将多个装饰器应用于一个函数,但我很好奇是否有一些简单的方法可以将两个装饰器组合成一个新的。

【问题讨论】:

标签: python decorator


【解决方案1】:

有点笼统:

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 首先被执行。
  • @如何为接受 args 和 kwargs 的装饰器做同样的事情?
【解决方案2】:

是的。请参阅装饰器的定义,here

这样的事情应该可以工作:

def multiple_decorators(func):
   return decorator1(decorator2(func))

@multiple_decorators
def foo(): pass

【讨论】:

  • 谢谢,也是一个有用的链接。我选择了更通用的解决方案。干杯。
  • 我喜欢这个解决方案的简洁性,并且发现它对我的项目很有帮助。
  • Ditto. 虽然公认的答案 is 对于一般情况来说确实很棒,但这个答案简明扼要地展示了一个装饰器服从多个其他装饰器,其名称为在解释时静态已知。由于这是通常的情况,这也很棒! 大家都赞成。
【解决方案3】:

装饰器只是将函数作为输入并返回新函数的函数。这个:

@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():
    ...

【讨论】:

    【解决方案4】:

    如果装饰器不接受额外的参数,你可以使用

    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
    

    【讨论】:

    • 这不是“对一个函数应用多个装饰器”吗?
    • @delnan:这不是“将两个 [装饰器] 组合成一个新装饰器的简单方法”吗? :)
    • 谢谢。其中一个装饰器接受了参数,但另一个答案也是如此。
    【解决方案5】:

    如果你不想在测试套件中重复太多,你可以这样做::

    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)
    

    【讨论】:

      【解决方案6】:

      并扩展@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
      

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

      【讨论】:

        猜你喜欢
        • 2013-06-09
        • 2017-10-15
        • 1970-01-01
        • 2023-01-25
        • 2019-05-13
        • 1970-01-01
        • 2020-06-24
        • 2015-02-22
        相关资源
        最近更新 更多