Python Decorator 3.0和装饰器的参数

时间:2009-06-29 20:00:53

标签: python decorator

我很高兴看到decorator python模块(3.0)的最新版本。它比以前的迭代看起来更清晰(例如语法比以前更含糖)。

然而,对于那些自己提出论点的装饰者来说,似乎有糟糕的支持(例如“酸性”语法,可怕地延伸隐喻)。有没有人有一个很好的例子说明你如何使用decorator 3.0干净利落地做到这一点?

 def substitute_args(fun, arg_sub_dict):
      def wrapper(arg):
         new_arg = arg_sub_dict.get(arg, arg)
         return fun(new_arg)

      # some magic happens here to make sure that type signature, 
      # __name__, __doc__, etc. of wrapper matches fun

 return wrapper

2 个答案:

答案 0 :(得分:7)

在这种情况下,您需要让函数返回装饰器。 (任何事情都可以通过另一层次的间接解决......)

from decorator import decorator
def substitute_args(arg_sub_dict):
  @decorator
  def wrapper(fun, arg):
    new_arg = arg_sub_dict.get(arg, arg)
    return fun(new_arg)
  return wrapper

这意味着substitute_args本身不是装饰者,它是装饰者工厂。这是没有decorator模块的等价物。

def substitute_args(arg_sub_dict):
  def my_decorator(fun):
    def wrapper(arg):
      new_arg = arg_sub_dict.get(arg, arg)
      return fun(new_arg)
    # magic to update __name__, etc.
    return wrapper
  return my_decorator

三级深度不是很方便,但记住其中两个是定义函数的时候:

@substitute_args({}) # this function is called and return value is the decorator
def f(x):
  return x
# that (anonymous) decorator is applied to f

相当于:

def f(x):
  return x
f = substitude_args({})(f) # notice the double call

答案 1 :(得分:-2)

这是我刚发现的另一种方式:检查装饰器的第一个(也是唯一的)参数是否可调用;如果是这样,你就完成了并且可以返回你的行为修改包装器方法(它自己用functools.wraps修饰以保留名称和文档字符串)。

在另一种情况下,应存在一个或多个命名或位置参数;你可以收集这些参数并返回一个callable,它接受一个可调用的第一个参数并返回一个包装器方法 - 因为该描述符合装饰器方法的描述,所以返回那个非常装饰器的方法!我在这里使用functools.partial来获取我的装饰者is_global_method的版本(我现在正在处理它 - 它的实现当然是无稽之谈,如下所示,这只是为了展示装饰作品)。

此解决方案似乎可行,但肯定需要更多测试。如果你睁开眼睛,你可以看到诀窍只有三四行作为一种记忆模式。现在我想知道我是否可以将这种功能包装到另一个装饰器中?啊,它的元素化!

from functools import wraps
from functools import partial

_               = print
is_instance_of  = isinstance
is_callable     = lambda x: hasattr( x, '__call__' )

def is_global_method( x, *, name = None ):
  if is_callable( x ):
    @wraps( x )
    def wrapper( *P, **Q ):
      return { 'name': name, 'result': x( *P, **Q ), }
    return wrapper
  # assert is_instance_of( x, str ) # could do some sanity checks here
  return partial( is_global_method, name = x )

@is_global_method
def f( x ):
  """This is method f."""
  return x ** 2

@is_global_method( 'foobar' )
def g( x ):
  """This is method g."""
  return x ** 2

_( f.__name__ )
_( f.__doc__ )
_( f( 42 ) )
_( g.__name__ )
_( g.__doc__ )
_( g( 42 ) )