【问题标题】:Decorator returns function object instead of output of wrapped function装饰器返回函数对象而不是包装函数的输出
【发布时间】:2019-05-31 01:13:35
【问题描述】:

抱歉,我只能想象 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)

【问题讨论】:

    标签: python decorator python-decorators


    【解决方案1】:

    你的装饰器是一个装饰器工厂,它返回另一个装饰器。这里不需要工厂,去掉一层:

    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),但在您的版本中 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
    

    现在你必须在装饰时使用@continual_retry()@continual_retry(&lt;integer&gt;),例如:

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

    因为是continual_retry() 产生了装饰器,而continual_retry(3)(divide) 产生了替换原始函数的包装器。

    【讨论】:

    • 像往常一样,Martijn,你超越并提供了精彩的解释。那是我缺少的心智模型的一部分,我正在模仿的样板代码是一个装饰工厂。感谢您抽出宝贵时间向我说明这一切!
    猜你喜欢
    • 2020-04-28
    • 1970-01-01
    • 2020-12-04
    • 2017-09-13
    • 1970-01-01
    • 2016-10-20
    • 1970-01-01
    • 2021-07-13
    • 2019-11-05
    相关资源
    最近更新 更多