简体   繁体   English

Python Decorator 3.0和装饰器的参数

[英]Python Decorator 3.0 and arguments to the decorator

I'm excited to see the latest version of the decorator python module (3.0). 我很高兴看到最新版本的decorator python模块(3.0)。 It looks a lot cleaner (eg the syntax is more sugary than ever) than previous iterations. 与以前的迭代相比,它看起来更清晰(例如语法比以前更加含糖)。

However, it seems to have lousy support (eg "sour" syntax, to horribly stretch the metaphor) for decorators that take arguments themselves. 然而,对于那些自己提出论点的装饰者来说,它似乎有糟糕的支持(例如“酸”语法,可怕地延伸隐喻)。 Does anyone have a good example for how you'd cleanly do this using 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

In this case, you need to make your function return the decorator. 在这种情况下,您需要使您的函数返回装饰器。 (Anything can be solved by another level of indirection...) (任何事情都可以通过另一层次的间接解决......)

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

This means substitute_args isn't a decorator itself, it's a decorator factory . 这意味着substitute_args本身不是装饰器,它是装饰工厂 Here's the equivalent without the decorator module. 这是没有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

Three levels deep isn't very convenient, but remember two of them are when the function is defined: 三层深度不是很方便,但记住其中两个是定义函数的时候:

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

Which is equivalent to: 这相当于:

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

here is another way i have just discovered: check whether the first (and only) argument to your decorator is callable; 这是我刚刚发现的另一种方式:检查装饰器的第一个(也是唯一的)参数是否可调用; if so, you are done and can return your behavior-modifying wrapper method (itself decorated with functools.wraps to preserve name and documentation string). 如果是这样,你就完成了并且可以返回你的行为修改包装器方法(它本身用functools.wraps修饰以保留名称和文档字符串)。

in the other case, one or more named or positional arguments should be present; 在另一种情况下,应该存在一个或多个命名或位置参数; you can collect those arguments and return a callable that accepts a callable as first argument and returns a wrapper method—and since that description fits the description of the decorator method, return that very decorator method! 你可以收集这些参数并返回一个callable,它接受一个可调用的第一个参数并返回一个包装器方法 - 因为该描述符合装饰器方法的描述,所以返回那个非常装饰器的方法! i've used functools.partial here to get a version of my decorator, is_global_method (which i'm working on right now—its implementation is of course nonsense as shown below, this is only to demonstrate the decoration works). 我在这里使用functools.partial获取我的装饰器的版本, is_global_method (我现在正在处理它 - 它的实现当然是废话,如下所示,这只是为了演示装饰效果)。

this solution appears to work but sure needs more testing. 这个解决方案似乎有效,但肯定需要更多测试。 if you quint our eyes, you can see that the trick is only three or four lines as a pattern to remember. 如果你睁开眼睛,你可以看到诀窍只有三四行作为一种记忆模式。 now i wonder whether i can wrap that kind of functionality into another decorator? 现在我想知道我是否可以将这种功能包装到另一个装饰器中? ah, the metaness of it! 啊,它的元素化!

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

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

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