Activation of neurons Welcome back to the discussion on deep learning. We could have a basic idea of what is deep learning in the...
Handy Python Features – Part 02admin
I assume that part 01 of this series gave you a better understanding of how decorators work and where to use them. We are ready to explore more on decorators in this discussion.
There are few metadata maintained withing a function for several purposes. For example, we can keep some notes for documentation when a function is defined. These notes are stored as metadata in the __doc__ field which would be used by IDEs. We can use built-in function help(*args, **kwargs) to test this.
This would use __doc__ and __name__ metadata for presenting the result as follows.
Effect with decorators
Once a function is decorated, those metadata would get lost.
The results are not as expected. The metadata of the actual function is lost at this point as shown below.
To overcome this issue, we need to pass the original metadata to the decorator. One way of doing this is resetting the decorator attributes with original attributes. This is possible since we have access for those fields via function reference.
This would reset the metadata and the function help(hello_world) would display correct notes as before. However, there is a better way of doing this. Python provides a module named functools and it has a decorator called @wrap(function) which takes the original function as an attribute and do the rest as we did above.
We saw how the functions are decorated up to now. Decorator trick works not only for functions but also for many other objects which are callables. Before exploring further, we need to understand what are callables.
Callables in python are anything that can be called. For example, functions are callable. Classes are callable. We can call a class to create an instance of it. What else are callable?
How to check
There is a built-in function in Python named callable(object) which can check whether a given object is callable.
We will use this method and further explore what objects are callables as in the following example.
The results show that function hello_world() is callable. Other than that, lamdas, classes, class methods are callable. But, instances of classes, strings, floats, booleans are not callable.
Making a class instance callable
In python, an instance of a class can be made callable. There is a special method named __call__(self, *args, **kwargs) that a class should define in order to make its instances callable.
The instance of class Animal becomes callable after introducing method __call__(self, *args, **kwargs).
Decorators with callables
The decorators can take any callable as an argument and return a callable. Now we know that classes and class instances are callables. This enables us using them with decorators.
Classes as decorators
We will use a class as a decorator for making an in-memory cache.
In this example, the class Cache is used as a decorator for the function remote_call(url). Suppose this method does a remote call to fetch some results. We need to cache those results in order to prevent the remote call which might be time-consuming. The dictionary url_result_dict kept in the class Cache is used to store the results.
A class object is a callable as we found above. But a decorator should take a callable as an input. The only way it can accept input is via the constructor, the method __init__(self, function). It is clear that an instance of the class Cache is going to be created now. To make that instance callable, we need to implement the method __call__(self, *args, **kwargs) as in the previous discussion. Rest of the things are clear. The cache is working as expected.
The results for endpoint-1 is coming from the function remote_call(url) for the first time. The cache is used for the second time. The results for endpoint-2 is again from the function. The final result is from cache since the endpoint-1 was fetched earlier.
Instances as decorators
We can use an instance of a class as a decorator too. The constructor was used to take the callable argument in the above example. But the method __call__(self, function) is used this time.
Here the object logger is callable and acting as a decorator. It returns the inner function which is capable of logging based on the boolean enabled.The results are as follows.
The decorators can be used multiple times for the same callable. We will use both of the above decorators in the following example.
The decorator logger returns a callable and it is passed to the decorator Cache. This chaining is possible for any number of decorators. The results are as follows.
Following example shows how to implement singleton pattern using decorators.
The class Singleton is keeping an instance of the decorated class SingletonLogger which is created when the method instance() is called. Creating instances using constructor is blocked using the method __call__(self).
Further reading …
Decorators are handy. We need to identify where to use them without making complex codes. I would suggest you read more on this topic.
There are more handy features in Python. Generators are one of them. Follow the part o3 of this series for more fun.