You might have seen this one before – you wrote a decorator in Python and tried to apply it to a class method (or static method, for that matter), only to see an error.
from functools import wraps def logged(func): """A decorator printing a message before invoking the wrapped function.""" @wraps(func) def wrapped_func(*args, **kwargs): print('Invoking', func) return func(*args, **kwargs) return wrapped_func class Foo(object): @logged @classmethod def get_name(cls): return cls.__name__
As the docstring explains, the logged
decorator simply prints a message before invoking the decorated function, and it is applied to the get_name()
class method of the class Foo
. The @wraps
decorator makes sure the original function’s metadata is copied to the wrapper function returned by the decorator (docs).
But despite this essentially being a textbook example of a decorator in Python, invoking the get_name()
method results in an error (using Python3 below):
>>> Foo.get_name() Invoking <classmethod object at 0x7f8e7473e0f0> Traceback (most recent call last): ... TypeError: 'classmethod' object is not callable
If you just want to quickly fix this issue, because it annoys you, here’s the TL;DR fix – just swap the order of the decorators, making sure that the @classmethod
decorator is applied last:
class Foo(object): @classmethod @logged def get_name(cls): return cls.__name__ >>> Foo.get_name() Invoking <function Foo.get_name at 0x7fce90356c80> 'Foo'
On the other hand, if you are curious what is actually happening behind the scenes, please keep reading.
The first thing to note is the output in each example immediately after calling Foo.get_name()
. Our decorator prints the object that is about to invoke in the very next line, and in the non-working example that object is actually not a function!
Invoking <classmethod object at 0x7f8e7473e0f0>
Instead, the thing that our decorator tries to invoke is a “classmethod” object, but the latter is not callable, causing the Python interpreter to complain.
Meet descriptors
Let’s take a closer look at a stripped-down version of the Foo
class:
class Foo(object): @classmethod def get_name(cls): return cls.__name__ >>> thing = Foo.__dict__['get_name'] >>> thing <classmethod object at 0x7f295ffc6d30> >>> hasattr(thing, '__get__') True >>> callable(thing) False
As it turns out, get_name
is an object which is not callable, i.e. we can not say get_name()
and expect it to work. By the presence of the __get__
attribute we can also see, that it is a descriptor.
Descriptors are object that behave differently than “normal” attributes. When accessing a descriptor, what happens is that its __get__()
method gets called behind the scenes, returning the actual value. The following two expressions are thus equivalent:
>>> Foo.get_name <bound method Foo.get_name of <class '__main__.Foo'>> >>> Foo.__dict__['get_name'].__get__(None, Foo) <bound method Foo.get_name of <class '__main__.Foo'>>
__get__()
gets called with two parameters – the object instance the attribute belongs to (None
here, because accessing the attribute through a class), and the owner class, i.e. the one the descriptor is defined on (Foo
in this case)1.
What the classmethod descriptor does is binding the original get_name()
function to its class (Foo
), and returning a bound method object. When the latter gets called, it invokes get_name()
, passing class Foo
as the first argument (cls
) along with any other arguments the bound method was originally called with.
Armed with this knowledge it is now clear why our logged
decorator from the beginning does not always work. It assumes that the object passed to it is directly callable, and does not take the descriptor protocol into account.
Making it right
Describing how to adjust the logged
decorator to work correctly is quite a lengthy topic, and out of scope of this post. If interested, you should definitely read the blog series by Graham Dumpleton, as it addresses many more aspects than just working well with classmethods. Or just use his wrapt library for writing decorators:
import wrapt @wrapt.decorator def logged(wrapped, instance, args, kwargs): print('Invoking', wrapped) return wrapped(*args, **kwargs) class Foo(object): @logged @classmethod def get_name(cls): return cls.__name__ >>> Foo.get_name() Invoking <bound method Foo.get_name of <class 'main2.Foo'>> 'Foo'
Yup, it works.
-
On the other hand, if retrieving a descriptor object directly from the class’s
__dict__
, the descriptor’s__get__()
method is bypassed, and that’s why we usedFoo.__dict__['get_name']
at a few places in the examples. ↩