2016-11-04 79 views
2

我有两个装饰器。每个装饰器都有一个作为参数的函数。每个装饰器为该功能设置一个属性。在单个函数上链接装饰器之后,我希望看到2个新属性。但是,顶级装饰器t2会“覆盖”属性t1集合。否则,在解决所有问题后,t1不再存在。任何人都可以解释为什么以及如何解决它?Python链式装饰器覆盖属性

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 
+1

't1'“包装”功能'test',然后't2'“包装”的函数由't1'返回。因此't2'应该期望一个*修饰*函数作为它的参数 - 而不是'测试'。 –

+1

作为一种文体学观点,[PEP8标准](https://www.python.org/dev/peps/pep-0008/#indentation)要求4个空格用于缩进。虽然只有1个在语法上起作用,但它很难阅读,并且很难与其他人分享您的代码。 –

+0

是的,我明白了。如果我dir(函数)t2的输入,我看到属性t1。但是,如果我dir(测试),我只看到t2(t1被删除)。 – Sonny

回答

3

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

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 
+0

或者更好的是,使用'functools.wraps'来保存装饰函数的元数据。 – thorhunter

+0

@thorhunter是的,尽管据我所知,'wraps'并不复制所有属性。 –

+0

啊,好的,所以链接装饰(包装)产生包裹的包装,这就是为什么我看不到t1。这现在更有意义了。谢谢。 – Sonny