python - 通过堆叠装饰器传递函数参数

时间:2017-10-11 12:25:44

标签: python python-decorators

我正在尝试编写两个独立但可堆叠的装饰器,一个用于在方法之前和之后打印对象的状态,另一个用于在方法之后运行一些内部类测试(也有参数)。

这是我当前尝试的一个例子:

import functools

class Dog:

    def __init__(self):
        self.happy = False
        self.has_stick = False

    def __str__(self):
        n = ' ' if self.happy else ' not '
        return "I'm%sa happy dog" % n

    def _verbose(func):
        fname = func.func_name
        argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
        @functools.wraps(func)
        def decorator(*args, **kwargs):
            print "Before %s(%s):" % (fname, ', '.join(
                '%s=%r' % entry
                for entry in zip(argnames, args)[1:] + kwargs.items()))
            print args[0]
            result = func(*args, **kwargs)
            print "After %s(%s):" % (fname, ', '.join(
                '%s=%r' % entry
                for entry in zip(argnames, args)[1:] + kwargs.items()))
            print args[0]
            return result
        return decorator

    def _test(printout):
        def actual_decorator(func):
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                self = args[0]
                output = func(*args, **kwargs)
                self._test_not_happy_without_stick(printout)
                return output
            return wrapper
        return actual_decorator

    def _test_not_happy_without_stick(self, printout):
        if printout:
            print "Is happy:", self.happy
            print "Has stick:", self.has_stick
        if self.happy and not self.has_stick:
            print "ERROR"

    @_test(True)
    @_verbose
    def finds_stick(self, good_stick):
        print "I found a stick!"
        self.happy = good_stick
        self.has_stick = True

if __name__ == '__main__':
    fido = Dog()
    fido.finds_stick(False)

如果如上所述应用装饰器的顺序,则输出为:

Before finds_stick(good_stick=True):
I'm not a happy dog
I found a stick!
After finds_stick(good_stick=True):
I'm a happy dog
Is happy: True
Has stick: True

但是,如果它被反转(正如我想做的那样),传递给修饰函数的参数的名称和值将丢失,如下所示:

Before finds_stick():
I'm not a happy dog
I found a stick!
Is happy: True
Has stick: True
After finds_stick():
I'm a happy dog

如何确保任何此类装饰器堆叠不会阻止参数通过装饰器传递?

或者,我很乐意提出一个更加pythonic的方法来解决这个问题。

1 个答案:

答案 0 :(得分:0)

原来问题是由于@functools.wrap()的向后兼容性,它不保留签名以实现我上面所需的(就像在Python 3.4 +中那样)。

但是,您可以使用decorator包来获得所需的功能,如here所述。

上面的正确代码是:

import decorator

class Dog:

    def __init__(self):
        self.happy = False
        self.has_stick = False

    def __str__(self):
        n = ' ' if self.happy else ' not '
        return "I'm%sa happy dog" % n

    @decorator.decorator
    def _verbose(func, *args, **kwargs):
        fname = func.func_name
        argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
        print "Before %s(%s):" % (fname, ', '.join(
            '%s=%r' % entry
            for entry in zip(argnames, args)[1:] + kwargs.items()))
        print args[0]
        result = func(*args, **kwargs)
        print "After %s(%s):" % (fname, ', '.join(
            '%s=%r' % entry
            for entry in zip(argnames, args)[1:] + kwargs.items()))
        print args[0]
        return result

    def _test(printout):
        @decorator.decorator
        def wrapper(func, *args, **kwargs):
            self = args[0]
            output = func(*args, **kwargs)
            self._test_not_happy_without_stick(printout)
            return output
        return wrapper

    def _test_not_happy_without_stick(self, printout):
        if printout:
            print "Is happy:", self.happy
            print "Has stick:", self.has_stick
        if self.happy and not self.has_stick:
            print "ERROR"

    @_verbose
    @_test(True)
    def finds_stick(self, good_stick):
        print "I found a stick!"
        self.happy = good_stick
        self.has_stick = True

if __name__ == '__main__':
    fido = Dog()
    fido.finds_stick(True)

输出:

Before finds_stick(good_stick=True):
I'm not a happy dog
I found a stick!
Is happy: True
Has stick: True
After finds_stick(good_stick=True):
I'm a happy dog