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

时间:2011-05-09 17:32:32

标签: python

我想编写一个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 个答案:

答案 0 :(得分: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)等。)

答案 1 :(得分: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

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

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

>>> foo()
1
2
3

但是,如果我们没有使用参数调用,我们将返回原始函数:

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

>>> foo(3)
<<< 3