【问题标题】:Python chaining decorators overwrite attributesPython链接装饰器覆盖属性
【发布时间】:2017-03-18 14:40:03
【问题描述】:

我有两个装饰器。每个装饰器都获得一个函数作为参数。每个装饰器都为函数设置一个属性。在将装饰器链接到单个函数后,我希望看到 2 个新属性。但是,顶部装饰器 t2 “覆盖”了属性 t1 集。否则,在一切都解决后,t1 不再存在。谁能解释一下为什么以及如何解决它?

def t1(function):
 def wrapper(*args, **kwargs):
  setattr(wrapper, "t1", True)
  return function(*args, **kwargs)
 setattr(wrapper, "t1", False)
 return wrapper

def t2(function):
 def wrapper(*args, **kwargs):
  setattr(wrapper, "t2", True)
  return function(*args, **kwargs)
 setattr(wrapper, "t2", False)
 return wrapper

@t2
@t1
def test():
 pass

【问题讨论】:

  • t1“包装”函数test,然后t2“包装”由t1返回的函数。因此t2 应该期望一个 decorated 函数作为它的参数——而不是test
  • 作为文体方面的一点,the PEP8 standard 要求缩进 4 个空格。虽然只有 1 在语法上有效,但它真的很难阅读,并且很难与他人共享您的代码。
  • 是的,我明白了。如果我 dir(function) t2 的输入,我会看到属性 t1。但是,如果我 dir(test),我只会看到 t2(t1 被擦除)。
  • 对不起,缩进,我正在做一个小显示器。

标签: python python-2.7 python-decorators


【解决方案1】:

它发生了,因为你的装饰器在包装器上设置了属性。当第一个装饰器在其包装器上设置属性时,它将包装器传递给第二个装饰器,第二个装饰器在第一个包装器的顶部添加另一个包装器并在第二个包装器上设置属性。所以你最终得到了第二个包装器。

In [3]: def decorator_a(fn):
   ...:     def wrapper(*args, **kwargs):
   ...:         return fn(*args, **kwargs)
   ...:     print("I'm setting the attribute on function {}".format(id(wrapper)))
   ...:     setattr(wrapper, "attr1", True)
   ...:     return wrapper
   ...: 

In [4]: def decorator_b(fn):
   ...:     def wrapper(*args, **kwargs):
   ...:         return fn(*args, **kwargs)
   ...:     print("I'm setting the attribute on function {}".format(id(wrapper)))
   ...:     setattr(wrapper, "attr2", True)
   ...:     return wrapper
   ...: 

In [5]: first_time_decorated = decorator_a(lambda x: x)
I'm setting the attribute on function 4361847536

In [6]: second_time_decorated = decorator_b(first_time_decorated)
I'm setting the attribute on function 4361441064

您可以通过在包装器上设置正在装饰的函数的所有属性来解决此问题

In [14]: def decorator_a(fn):
    ...:     def wrapper(*args, **kwargs):
    ...:         return fn(*args, **kwargs)
    ...:     setattr(wrapper, "attr1", True)
    ...:     for attribute in set(dir(fn)) - set(dir(wrapper)):
    ...:         setattr(wrapper, attribute, getattr(fn, attribute))
    ...:     return wrapper
    ...: 

In [15]: def decorator_b(fn):
    ...:     def wrapper(*args, **kwargs):
    ...:         return fn(*args, **kwargs)
    ...:     setattr(wrapper, "attr2", True)
    ...:     for attribute in set(dir(fn)) - set(dir(wrapper)):
    ...:         setattr(wrapper, attribute, getattr(fn, attribute))
    ...:     return wrapper
    ...: 

In [16]: first_time_decorated = decorator_a(lambda x: x)

In [17]: second_time_decorated = decorator_b(first_time_decorated)

In [18]: second_time_decorated.attr1
Out[18]: True

In [19]: second_time_decorated.attr2
Out[19]: True

【讨论】:

  • 或者更好的是,使用functools.wraps 来保存修饰函数的元数据。
  • @thorhunter 是的,但据我所知,wraps 不会复制所有属性。
  • 啊,好的,所以链接装饰器(包裹)会产生包裹包裹,这就是我看不到 t1 的原因。这现在更有意义了。谢谢。
猜你喜欢
  • 2020-07-24
  • 2013-01-19
  • 1970-01-01
  • 2017-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-15
  • 2022-11-09
相关资源
最近更新 更多