Customisable class-based decorators#
To close off this section, the only thing missing is taking a look at the implementation pattern that the decorator lru_cache uses, since lru_cache itself is a function but, once applied to a function, you get an instance of another class that is not a function:
from functools import lru_cache
print(type(lru_cache)) # <class 'function'>
@lru_cache
def f():
pass
print(type(f)) # <class 'functools._lru_cache_wrapper'>
As shown above, the decorator lru_cache is a function but, once used as a decorator, it replaces the decorated functions with instances of this class called _lru_cache_wrapper.
The decorator lru_cache accepts arguments, and in a class-based decorator, the most reasonable way of accepting arguments is as arguments to __init__; after all, that’s the way you’re supposed to customise classes…
So, tentatively, you could add a parameter maxsize to your class-based decorator cache:
class cache:
def __init__(self, fn, maxsize):
self.fn = fn
self.maxsize = maxsize
# ...
# ...
However, just like when you were figuring out how to add arguments to decorators, this breaks the ability to use cache as a decorator directly:
@cache
def f():
...
# Exception raised:
Traceback (most recent call last):
File "<python-input-8>", line 1, in <module>
@cache
^^^^^
TypeError: cache.__init__() missing 1 required positional argument: 'maxsize'
Instead of putting the class cache inside of a function, as we did before, the standard library opts for giving it another name and leaving it in the global scope while creating another function that acts as the decorator:
class cache_wrapper:
def __init__(self, fn, maxsize):
self.fn = fn
self.maxsize = maxsize
# ...
# ...
def cache(maxsize_or_fn=None):
if callable(maxsize_or_fn): # (1)
fn = maxsize_or_fn
return cache_wrapper(fn, None)
else: # (2)
maxsize = maxsize_or_fn
def decorating_function(fn):
return cache_wrapper(fn, maxsize)
return decorating_function
The decorator cache is prepared to be used as @cache, @cache(), and @cache(1000):
If it’s used as
@cache, then the argumentmaxsize_or_fnis a function and the conditional statement in(1)is entered. You can return an instance of the classcache_wrapperwith the function given and the default value for the other argument(s) of the decorator.If it’s used as
@cache()or@cache(1000), then the argumentmaxsize_or_fnis supposed to be interpreted as the maximum size of the cache, in which case you enter theelsein(2)and you create a decorating function that will be immediately applied to the function to be decorated, which in turn returns an instace ofcache_wrapper.
Now that you have all the tools to create class-based decorators, you will put your understanding to the test.