有没有一种方法可以“解决”装饰器的保留状态?

时间:2019-02-28 16:53:04

标签: python python-3.x decorator

这是怎么回事?...

所以,这是我的情况。我正在准备一个Web剪贴簿,以便在此脚本的某个点上,我决定使用装饰器来处理一些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的文章,但是我无法弄清楚如何“重新启动”此装饰器,以使其在迭代过程中收到新的参数。 / p>

如果可能的话,有人会提示如何解决此问题吗?

1 个答案:

答案 0 :(得分:2)

之所以发生这种情况,是因为您正在重新装饰已经装饰的功能。

第一次迭代后:

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

但是...那只是一个可怕的烂摊子,暴露出一堆内部细节,最好留在引擎盖下。

从根本上说,我不确定为什么这里需要装饰器,即使用该装饰器有什么好处?