Декоратор возвращает объект функции вместо вывода обернутой функции - PullRequest
0 голосов
/ 04 января 2019

Извинения, я могу только представить, что StackOverflow полон людей, которые почти там, но все еще не совсем понимают декораторы.

Я пытаюсь украсить ряд функций, связанных с ОС, так что еслиесть какие-то исключения, такие как 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 голосов
/ 04 января 2019

Ваш декоратор является декоратором фабрики , который возвращает другого декоратора. Здесь вам не нужна фабрика, удалите один слой:

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(<integer>) при оформлении, например ::

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

, поскольку continual_retry() создает декоратор, а continual_retry(3)(divide) создает оболочку, заменяющую исходную функцию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...