Python Function Decorators 2: Decorating a Class Method

I think I need to make things a little bit clearer with the code shown in previous post...

The Decoration

Decorating a function enables you to call other code before the original function is called, or you can completely change the functions code. As I've red somewhere it is best equated to preprocessor macros or template libraries in C++.

In my previous post, I've decorated my class methods by adding @CachedMethod at the definition of the class method. This is one of the two ways of defining decorators. When I do that the Python interpreter decorates the unbound function, which means, it is decorated whenever I've imported the module not when I've created the instance object. This leads us to a little but important difference that can easily be neglected, so be careful to not to miss what you intended to do. I mean, when I've first written the CachedMethod class, it was caching the data in the Class not in the instance object. So every instance of that class was trying to use the same cached data, which in my situation was a wrong behaviour, though it is may desirable in some other cases...

By decorating my functions with a class (you can decorate them with functions too), I'm practically turning them in to classes. So instead of being a method of a class, they become a class belonging to another class.

The key to decorate a Class method in a way to be able to reach the instance object is to store the instance object in the __get__ method of the decorator and then use it in the __call__ method.

The order of execution of the CachedMethod classes methods are like that: when you import your module the functions are decorated, and the __init__ of the CachedMethod runs for all of the class methods you have decorated. Then when you create your object nothing happens, but when you call a decorated method, first the __get__ method runs, than the __call__ method. It is a good practice to add print() commands to the decorators method to understand what is going on.

The __get__ method has 3 arguments self, instance and class. Self, as regular, is the CachedMethod instance, instance is the instance of which the decorated function belongs to, and the class is the super that the instance is derived from.

You can change the settings of the arguments of __call__ method, but I think the best settings is a couple of *args, **keys together ( def __call__(self, *args, **keys) ) . So you can get any type of arguments setup passed to your original function and use them in your decorator. Other than this setup you can use the same argument count and order in your original function, but I don't recommend that, because you are restricted to decorate only the methods that has the same types of arguments.

CachedMethod Explained

Here are the brief explanation of what is going on in the methods of CachedMethod class.

__init__ : I store unbound method and the name of the unbound method in class variables, to let my self to call the original function later on.

__get__ : as far as I know, this is the only place that I can get the instance object. I store the instance object in self._obj and check if it already has its attributes set up. If not I create the attributes by using setattr commands

__call__ : I've all the data I need to have, so I do the caching by calling the original function if the self._data is empty or the time delta is bigger than the self._maxTimeDelta, otherwise I return the cached data. To get the data from the instance appropriately I use getattr, and I evaluate the attribute name on the fly by using self._name + '_data' for example.

Thats all... Hope it helps you to understand what is going on. And if not, there are some nice tutorials here and here is a nice function decorator library in Python.org

Comments