Decorator返回函数对象而不是包装函数的输出

pro*_*der 0 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
Run Code Online (Sandbox Code Playgroud)

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

>>> divide(1, 2)
<function __main__.divide(a, b)>
Run Code Online (Sandbox Code Playgroud)

我期待的结果

0.5
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*ers 5

你的装饰器是一个装饰工厂,它返回另一个装饰器.你在这里不需要工厂,删除一层:

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
Run Code Online (Sandbox Code Playgroud)

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

当Python遇到时@continual_retry,它将函数对象传递给continual_retry()callable以将结果替换为函数,就像你期望的那样divide = continual_retry(divide),但是在你的版本中continual_retry(divide)返回retry_decorated()函数,当调用它时,它本身最终返回func_wrapper对象.你想func_wrapper成为替代者.

当您想要配置装饰器时,您的双层方法很棒,其中外装饰器工厂函数接受函数以外的参数.我们的目标是使用它,@continual_retry(config_arg1, config_arg2, ...)以便Python首先调用该函数来获取返回值,然后通过调用返回值来进行修饰.

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

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
Run Code Online (Sandbox Code Playgroud)

现在你必须使用@continual_retry()@continual_retry(<integer>)装饰时,例如:

@continual_retry(3)
def divide(a, b):
    return a / b
Run Code Online (Sandbox Code Playgroud)

因为它continual_retry()产生装饰器,并continual_retry(3)(divide)产生替换原始函数的包装器.