带参数的Python装饰器,运行函数多次?

6 投票
2 回答
4646 浏览
提问于 2025-04-16 17:18

我想写一个Python装饰器,用来装饰一个unittest.TestCase里的测试函数,以决定这个函数应该在哪个目标主机上运行。看看这个例子:

class MyTestCase(unittest.TestCase):
    @target_host(["host1.com", "host2.com"])
    def test_my_command(self):
        #do something here against the target host

在这个被装饰的函数里,我希望能够让这个测试在所有主机上执行,我该怎么做呢?target_host的声明应该返回一个新函数,但能不能返回多个函数,让测试运行器去执行呢?

谢谢!

2 个回答

2

如果你想让你的装饰器可以接受参数,使用基于类的装饰器是个不错的选择。根据我的经验,这种方式更容易理解和维护。其实它们很简单,__init__ 方法可以接收装饰器的任何参数,而 __call__ 方法则返回被装饰的函数。写装饰器时一个需要注意的地方是,如果传入参数,装饰器的行为会完全不同。使用基于类的装饰器可以很容易地处理这个问题,让你的装饰器可以选择性地接受参数:

class target:
    def __init__(self, targets):
        """Arguments for decorator"""
        self.targets = None
        if not hasattr(targets, '__call__'):
            # check we are actually passed arguments!
            # if targets has __call__ attr, we were called w/o arguments
            self.targets = targets

    def __call__(self, f):
        """Returns decorated function"""
        if self.targets:
            def newf(*args, **kwargs):
                for target in self.targets:
                    f(target)
            return newf
        else:
            return f

现在如果我们使用带参数的装饰器,它会按预期工作,调用我们的函数三次:

>>> @target([1,2,3])
..: def foo(x): print x
...

>>> foo()
1
2
3

但是,如果我们没有传入参数,它就会返回原始的函数:

>>> @target       
def foo(x): print x
..: 

>>> foo(3)
<<< 3
11

你只能返回一个对象(技术上讲,你可以返回一组函数)。不过,如果你不想让大家感到惊讶,并且想要调用这个结果,最好还是返回一个单独的函数。不过,这个函数可以在一个循环中调用其他多个函数……你明白这意味着什么吗?

你需要一个装饰器工厂,这个工厂会返回一个闭包,这个闭包会在每组参数下调用它所应用的函数。代码示例如下(包括functools.wraps,用来保持函数的名称和文档字符串,这可能有用也可能没用,我通常默认加上):

def call_with_each(*arg_tuples):
    def decorate(f):
        @functools.wraps(f)
        def decorator():
            for arg_tuple in arg_tuples:
                f(*arg_tuple)
        return decorator
    return decorate

# useage example:
@call_with_each((3,), (2,)) # note that we pass several singleton tuples
def f(x):
    print x
# calling f() prints "3\n2\n"

支持关键字参数需要更多的代码,可能会有点复杂,但也是可以做到的。如果参数总是只有一个,那么代码可以简化(比如用def call_with_each(*args)for arg in args: f(arg),等等)。

撰写回答