Reputation: 23
I have two decorators. Each decorator gets a function as an argument. Each decorator sets an attribute to the function. After chaining the decorators on a single function, I would expect to see 2 new attributes. However, the top decorator t2 "overwrites" the attributes t1 sets. In otherwise, t1 no longer exists after everything is resolved. Can anyone explain why and how to fix it?
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
Upvotes: 2
Views: 172
Reputation: 10493
It happens, because your decorators set attributes on wrappers. When the first decorated sets the attribute on its wrapper, it passes the wrapper to the second decorater, that adds another wrapper on top of the first one and sets the attribute on the second wrapper. So you end up with the second wrapper.
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
You can solve this by setting all attributes of a function being decorated on the wrapper
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
Upvotes: 3