8.5 带参数的装饰器

    有时会希望传递更复杂的参数给装饰器。做法就是修改封装的函数。当我们采用这种方法时,装饰变成了一个两步的参数。

    下面的代码中,我们为一个函数提供了一个带参的装饰器。

    @decorator(arg)
    def func( ):
      pass

    上面装饰器的用法是下面代码的一种简化版本。

    def func( ):
      pass
    func= decorator(arg)(func)

    两个例子都做了下面3件事。

    • 定义了一个函数,func。
    • 将抽象的装饰器应用于它的参数上,创建了一个具体的装饰器,decorator(arg)。
    • 将具体的装饰器应用于函数上,创建了被装饰版本的函数,decorator(arg)(func)。

    这意味着带参的装饰器需要间接地创建最后的函数。再修改调试装饰器为下面这样。

    @debug("log_name")
    def some_function( args ):
      pass

    这段代码让我们可以指定调试日志所使用的名称。我们不使用根日志记录器,而是默认为每个函数提供一个独立的记录器。带参装饰器的大概结构如下。

    def decorator(config):
      def concrete_decorator(function):
        @functools.wraps( function )
        def wrapped( args, **kw ):
          return function(
    args, ** kw )
        return wrapped
      return concrete_decorator

    在开始介绍实例之前,先看看带参装饰器的内部细节。装饰器的定义(def decorator (config))规定了使用它时需要提供的参数。主体部分是具体的装饰器,它会作为整个装饰器的返回值。将被应用于函数上的是具体的装饰器(def concrete_ decorator(function))。然后,和之前看到的简单装饰器类似。它创建并返回了一个封装好的函数(def wrapped (args, *kw))。下面是带有记录器名称的调试装饰器。

    def debugnamed(logname):
      def concretedecorator(function):
        @functools.wraps( function )
        def wrapped( args, *kw ):
          log= logging.getLogger( logname )
          log.debug( "%s( %r, %r )", function.__name
    , args, kw, )
          result= function( args, *kw )
          log.debug( "%s = %r", function.__name
    , result )
          return result
        return wrapped
      return concrete_decorator

    decorator函数接受一个参数,即要使用的日志名称。它创建并返回了一个具体的装饰器函数。当它被应用于函数上时,具体的装饰器会返回一个封装好的函数。当函数以下面的方式使用时,装饰器会添加许多调试信息。它们直接输出到名为recursion的日志中,如下所示。

    @debug_named("recursion")
    def ackermann( m, n ):
      if m == 0: return n+1
      elif m > 0 and n == 0: return ackermann( m-1, 1 )
      elif m > 0 and n > 0: return ackermann( m-1, ackermann( m, n-1 ) )