装饰器返回函数对象,而不是包装函数的输出

时间:2019-01-04 12:54:12

标签: python decorator python-decorators

抱歉,我只能想象StackOverflow到处都是人,但仍然不太了解装饰器。

我正在尝试装饰一系列与os相关的功能,以便在出现FileNotFoundError或PermissionError之类的异常时,用户可以自己解决问题,然后重试。

因此,我已经创建了这个玩具功能和装饰器,但我不明白我在哪里没有正确地阅读我正在阅读的示例装饰器,并且在推理过程中遇到了麻烦:

from functools import wraps

def continual_retry(func):
    def retry_decorated(*args, **kwargs):
        @wraps(func)
        def func_wrapper(*args, **kwargs):
            while not stop:
                try:
                    func(*args)
                    stop = True
                except Exception as e:
                    print(f'Could not perform function {func.__name__}')
                    print(f' with args {repr(args)}')
                    print(f' due to error {e.class__}')    
                    redo = input('Retry (y/n)? ')      
                    if redo.lower() != 'y':
                        print('Exiting program due to error and user input')
                        sys.exit(0)
        return func_wrapper
    return retry_decorated

@continual_retry
def divide(a, b):
    return a/b

当我运行函数divide时,结果如下:

>>> divide(1, 2)
<function __main__.divide(a, b)>

我期待的结果

0.5

(然后我要测试divide(1, 0)

1 个答案:

答案 0 :(得分:5)

您的装饰器是一个装饰器 factory ,它返回另一个装饰器。您在这里不需要工厂,只需移除一层即可:

def continual_retry(func):
    @wraps(func)
    def func_wrapper(*args, **kwargs):
        while True:
            try:
                return func(*args, **kwargs)
            except Exception as e:
                print(f'Could not perform function {func.__name__}')
                print(f' with args {repr(args)}')
                print(f' due to error {e.class__}')    
                redo = input('Retry (y/n)? ')      
                if redo.lower() != 'y':
                    print('Exiting program due to error and user input')
                    sys.exit(0)
    return func_wrapper

您还需要返回函数结果,并且我将while循环更改为while True:的无穷循环,因为成功的return将退出循环。我还更新了对func()的调用,以传递关键字参数(return func(*args, **kwargs))。

Python遇到@continual_retry时,它将函数对象传递给continual_retry(),可调用该函数对象以结果替换函数,就像您期望的divide = continual_retry(divide)一样,但是在您的版本{{ 1}}返回continual_retry(divide)函数,该函数本身在被调用时最终会返回retry_decorated()对象。您希望使用func_wrapper来代替。

当您要配置装饰器时,您的双层方法非常有用,其中外部装饰器工厂函数接受函数以外的参数。目的是将其用作func_wrapper,以便Python首先调用该函数以获取返回值,然后通过调用该返回值进行修饰。

例如,您可以添加一个选项来限制重试次数:

@continual_retry(config_arg1, config_arg2, ...)

现在,必须在装饰时使用def continual_retry(limit=None): def retry_decorator(func): @wraps(func) def func_wrapper(*args, **kwargs): retries = 0 while True try: return func(*args, **kwargs) except Exception as e: print(f'Could not perform function {func.__name__}') print(f' with args {repr(args)}') print(f' due to error {e.class__}') redo = input('Retry (y/n)? ') if redo.lower() != 'y': print('Exiting program due to error and user input') sys.exit(0) retries += 1 if limit is not None and retries > limit: # reached the limit, re-raise the exception raise return func_wrapper return retry_decorator @continual_retry(),例如:

@continual_retry(<integer>)

因为是@continual_retry(3) def divide(a, b): return a / b 产生了装饰器,而continual_retry()产生了替换原始函数的包装器。