Есть ли способ «обойти» состояние хранения декоратора? - PullRequest
0 голосов
/ 28 февраля 2019

Что происходит? ...

Итак, это мой случай.Я разрабатываю веб-лом, чтобы в какой-то момент этого сценария я решил использовать декоратор для обработки некоторых URL-адресов.Этот декоратор имеет аргумент (URL), который должен динамически изменяться с помощью оператора цикла for, как показано в примере сценария:

from functools import wraps
import logging

logging.basicConfig(level=logging.INFO)

def cycle(url):
    def outer_wrapper(func):
        state = 0
        @wraps(func)
        def inner_wrapper(**kwargs):
            nonlocal state
            state += 1
            kwargs['url'] = url
            if state == 1:
                logging.info('Returning result at first execution on {} with: '
                             'state => {}, kwargs => {}'.format(func, state, kwargs))
                return func(**kwargs)
            else:
                logging.info('Returning result at upcoming executions on {} with: '
                             'state => {}, kwargs => {}'.format(func, state, kwargs))
                return func(**kwargs)

        return inner_wrapper
    return outer_wrapper


def print_url(url):
    print('Returned from print_url function:', url)

links = ['an-url', 'another-url']

for link in links:
    # Decorator
    print_url = cycle(link)(print_url)
    print_url()

Однако выходные данные показывают неожиданные результаты, по крайней мере для меня.

INFO:root:Returning result at first execution on <function print_url at 0x000002202FD68D08> with: state => 1, kwargs => {'url': 'an-url'}
Returned from print_url function: an-url
INFO:root:Returning result at first execution on <function print_url at 0x000002202FE196A8> with: state => 1, kwargs => {'url': 'another-url'}
INFO:root:Returning result at upcoming executions on <function print_url at 0x000002202FD68D08> with: state => 2, kwargs => {'url': 'an-url'}
Returned from print_url function: an-url

Декоратор сохраняет адресацию и аргумент первого вызова функции print_url().Я прочитал много статей о сборке мусора, слабых ссылках и стандартной библиотеке functools, но я не мог понять, как «перезапустить» этот декоратор, чтобы он получил новый аргумент в процессе итерации.

У кого-нибудь есть подсказка, как решить эту проблему - если это возможно?

1 Ответ

0 голосов
/ 28 февраля 2019

Это происходит потому, что вы перекрашиваете уже оформленную функцию.

После первой итерации:

for link in links:
    # Decorator
    print_url = cycle(link)(print_url)
    print_url()

Тогда print_url относится к inner_wrapper.Вы украшаете inner_wrapper снова.Это не имеет ничего общего с сборкой мусора, это просто то, что вы написали для этого.

Это становится более понятным, если вы удалите wraps:

def cycle(url):
    def outer_wrapper(func):
        state = 0
        def inner_wrapper(**kwargs):
            nonlocal state
            state += 1
            kwargs['url'] = url
            if state == 1:
                print('Returning result at first execution on {} with: '
                             'state => {}, kwargs => {}'.format(func, state, kwargs))
                return func(**kwargs)
            else:
                print('Returning result at upcoming executions on {} with: '
                             'state => {}, kwargs => {}'.format(func, state, kwargs))
                return func(**kwargs)

        return inner_wrapper
    return outer_wrapper


def print_url(url):
    print('Returned from print_url function:', url)

links = ['an-url', 'another-url']

for i, link in enumerate(links):
    print("Iteration :", i)
    print_url = cycle(link)(print_url)
    print_url()

И вывод терминала:

Iteration : 0
Returning result at first execution on <function print_url at 0x1060892f0> with: state => 1, kwargs => {'url': 'an-url'}
Returned from print_url function: an-url
Iteration : 1
Returning result at first execution on <function cycle.<locals>.outer_wrapper.<locals>.inner_wrapper at 0x106089378> with: state => 1, kwargs => {'url': 'another-url'}
Returning result at upcoming executions on <function print_url at 0x1060892f0> with: state => 2, kwargs => {'url': 'an-url'}
Returned from print_url function: an-url

Если вы хотите получить доступ к исходной функции, япредлагаем просто сохранить ссылку на него вне цикла и не присваивать результат декоратора этой переменной, например:

original_function = print_url
for link in links:
    # Decorator
    print_url = cycle(link)(original_function)
    print_url()
    print_url = original_function

просто для удовольствия, вы могли бы восстановить егона каждой итерации делали что-то с эффектом:

for link in links:
    # Decorator
    print_url = cycle(link)(print_url)
    print_url()
    closure = print_url.__closure__
    idx_func = print_url.__code__.co_freevars.index('func')
    print_url = closure[idx_func].cell_contents

Но ... это просто ужасный беспорядок, который раскрывает кучу внутренних деталей, которые лучше оставить под капотом.

Принципиально, Я не уверен, зачем здесь нужен декоратор, то есть в чем преимущество использования этого декоратора?

...