@classmethod defines a __wrapped__ attribute that always points to the inner most function in a decorator chain while functool's update_wrapper has been fixed to set the wrapper.__wrapped__ attribute after updating the wrapper.__dict__ (see https://bugs.python.org/issue17482) so .__wrapped__ points to the next decorator in the chain.
This results in inconsistency of the value of the.__wrapped__ attribute.
Consider this code:
from functools import update_wrapper
class foo_deco:
def __init__(self, func):
self._func = func
update_wrapper(self, func)
def __call__(self, *args, **kwargs):
return self._func(*args, **kwargs)
class bar_deco:
def __init__(self, func):
self._func = func
update_wrapper(self, func)
def __call__(self, *args, **kwargs):
return self._func(*args, **kwargs)
class Foo:
@classmethod
@foo_deco
def bar_cm(self):
pass
@bar_deco
@foo_deco
def bar_bar(self):
pass
print(Foo.bar_cm.__wrapped__)
# <function Foo.bar_cm at 0x7fb0254433a0>
print(Foo.bar_bar.__wrapped__)
# <__main__.foo_deco object at 0x7fb025445fd0>
# The foo_deco object is available on bar_cm this way though
print(Foo.__dict__['bar_cm'].__func__)
# <__main__.foo_deco object at 0x7fb025445fa0>
It would be more consistent if the fix that was applied to update_wrapper was ported to classmethod's construction (or classmethod could invoke update_wrapper directly, maybe). It's also worth noting that @staticmethod behaves the same and @property doesn't define a .__wrapped__ attribute. For @property, I don't know if this is by design or if it was just never ported, but I believe it would be a great addition just to be able to go down a decorator chain without having to special-case the code.
|