如何在Python中向装饰器传递特定参数

3

我希望编写一个Python函数装饰器,用于测试某些参数是否符合某种标准。例如,假设我想测试某些参数是否始终为偶数,则我希望能够像这样进行操作(不是有效的Python代码):

def ensure_even( n )  :
  def decorator( function ) :
    @functools.wraps( function )
    def wrapper(*args, **kwargs):
      assert(n % 2 == 0)
      return function(*args, **kwargs)
    return wrapper
   return decorator


@ensure_even(arg2)
def foo(arg1, arg2, arg3) : pass

@ensure_even(arg3)
def bar(arg1, arg2, arg3) : pass

但是我不知道如何实现上述需求。是否有一种方法可以向装饰器传递特定的参数?(就像上面的foo的arg2和bar的arg3一样)
谢谢!

制作参数化修饰器的唯一方法是使用嵌套装饰器。 - lprsd
2个回答

11

你可以这样做:

def ensure_even(argnum):
  def fdec(func):
    def f(*args, **kwargs):
      assert(args[argnum] % 2 == 0)  #or assert(not args[argnum] % 2)
      return func(*args, **kwargs)
    return f
  return fdec

那么:

@ensure_even(1)  #2nd argument must be even
def test(arg1, arg2):
  print(arg2)

test(1,2) #succeeds
test(1,3) #fails

是的,我确实这样做了,但我在想这只是我的C++背景想出了一个hack,而不是更优雅的Pythonic方式 :) 无论如何,谢谢。 - MK.
1
@MK:如果您使用关键字参数而不是位置参数,则可以将ensure_even传递给关键字参数的名称。这样做可能更好,因为会少一个硬编码的数字,并且您的代码可能会在添加新参数时而无需更改参数编号。 - unutbu
@~unutbu 是的,那是一个很好的方法。 - jcao219

4

之前的回答没有理解你问题的重点。

这里是一个更长的解决方案:

def ensure_even(*argvars):
  def fdec(func):
    def f(*args,**kwargs):
      for argvar in argvars:
        try:
          assert(not args[func.func_code.co_varnames.index(argvar)] % 2)
        except IndexError:
          assert(not kwargs[argvar] % 2)
      return func(*args,**kwargs)
    return f
  return fdec

以下是用法:

@ensure_even('a','b')
def both_even(a,b):
    print a,b

@ensure_even('even')
def first_even(even, arg2):
    print even, arg2

both_even(2,2)
first_even(2,3)
both_even(2,1) #fails

尽管我不确定它是否适用于所有情况。

啊,反射。我必须先去找出上面的代码在做什么,但我明白了,而且很酷。不过,我打算继续使用上面的解决方案。 - MK.

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