【问题标题】:Is there a way to "work around" the retention state of a decorator?有没有办法“解决”装饰器的保留状态?
【发布时间】:2019-07-22 15:49:09
【问题描述】:

发生了什么事?...

所以,这就是我的情况。我正在详细说明网络抓取,因此在此脚本的某个时刻,我决定使用装饰器来处理某些 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 的文章,但我不知道如何“重新启动”这个装饰器,以便在迭代过程中接收一个新参数。

如果可能的话,有人知道如何解决这个问题吗?

【问题讨论】:

  • 因为你正在重新装饰你已经装饰的函数

标签: python python-3.x decorator


【解决方案1】:

发生这种情况是因为您正在重新装饰已装饰的函数。

第一次迭代后:

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

但是......这只是一个可怕的混乱,暴露了一堆内部细节,最好留在引擎盖下。

从根本上说,我不知道为什么这里需要一个装饰器,也就是说,使用这个装饰器有什么好处?

【讨论】:

  • 嗯,这是一个非常快速的答案,确实解决了我的问题。谢谢!装饰器的目的是进行语法训练。我想习惯更复杂的编码方法。你的回答让我意识到我必须学习更多的装饰器,因为我真的不明白这个 for 循环之前的简单赋值如何解决我的问题!
  • @Dg_gt 再次,一旦你装饰了some_func = decorator(some_func),那么some_func 指的是装饰器返回的任何东西。在这种情况下,您返回一个包装器函数,并继续对其进行装饰。
猜你喜欢
  • 2020-04-15
  • 1970-01-01
  • 2014-09-30
  • 1970-01-01
  • 2019-06-16
  • 2012-05-11
  • 1970-01-01
  • 2021-08-01
  • 2011-11-22
相关资源
最近更新 更多