Having a Python class decorator work with self

In a project, I need to benchmark some things and one of those thing is to time how long it takes to have an answer. In my code, this means measuring how long it takes between two different methods of an object are called. As I’m learning Python, I thought that a beautiful to do that would be with a class decorator.

If you don’t know anything about class decorator, I advise you read chapter 38.4 “Coding Class Decorators” from the book Learning Python by Mark Lutz and if you don’t even know anything about decorators, shame on you! No, I’m kidding, then just read chapter 38.2.

In a nutshell, a decorator makes it possible to replace a function (or a class) with another by just writing @myDecorator before the function or class. In my case, by writing @TwoMethodsTimer(“requestMedia”, “startPlayback”) before the class I want to benchmark, I will gather statistics on how long it takes to start the playback once I requested a media. Here is a first version of this decorator that I did after reading chapter 38.4 and some other blog posts and stack overflow threads:

To use it on any class is very simple, here is an example:

In this example, we measure how long it takes between calling methodA and then methodB. It works great and all. We’re happy, until we do something new:

Here we see that internally calling methodB (self.methodB()) does not trigger the stopTimer! That’s because it doesn’t seem to be calling __getattr__ when being already in the same object. I tried several variant by intercepting calls to __getattribute__ but it’s a mess and when it’s not doing infinite recursive calls, I just don’t see the calls to the method I’m interested in.

To solve this problem, instead of hijacking calls for the __getattr__ method and looking at the name of the method being called, I just modify the attributes of the object when being created. Here is the code:

Just after the instantiation of the object, we change the mapping of the two methods we’re interested in with our own methods and store the old ones somewhere else. This trick works for external and internal method calls.