Python装饰器3.0以及用于装饰器的参数

6
我很高兴看到最新版本的Python模块decorator(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

装饰器主页:http://pypi.python.org/pypi/decorator/ - Roger Pate
2个回答

8
在这种情况下,您需要使函数返回装饰器。(任何问题都可以通过增加一层间接性来解决...)
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

-2

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

在另一种情况下,应该存在一个或多个命名或位置参数;你可以收集这些参数并返回一个可调用对象,它接受一个可调用对象作为第一个参数并返回一个包装方法——由于这个描述符符合装饰器方法的描述,因此返回这个非常的装饰器方法!我在这里使用了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 ) )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接