Meta-programming in Python: Decorator Classes
In this article we will look at how to write decorators using classes, we will also be covering how you can add parameters to your decorators: both decorator functions and decorator classes. If you do not know what decorators are, checkout the previous post on it in this series.
So how exactly do you create decorators using classes?
Classes in Python come with some nifty inbuilt functions that by give special properties to any class they get defined in. This functions — or since they are part of a class, you would have to refer to them as method by design — are called dunder methods. They are designed to be pretty easy to spot in any class because they are preceded and succeeded by two underscores. A typical and the most popular of a dunder method in python is the __init__ method
class Newclass
def __init__(self, *args):
pass def __len__(self, other):
pass#__len__ is another example of a dunder method
Important to note that the names giving to ‘dunder’ methods are not arbitrary, they have predetermined names, as such for them work properly, they must be defined properly, with the proper dunder method names — more on this in my python OOP series.
The dunder method we will be looking at for creating decorator classes is __call__. The function of the __call__ dunder method is to make it possible for instances of classes to be called in the same manner someone would call a function.
class Newclass
def __init__(self, name, age):
self.name = name
self.age = age def __call__(self, name):
if isinstance(name, str):
return name + self.name
return nameperson = Newclass('Onothoja') #creates an instance of Newclass
completed = person(' Marho')print(completed)#Onothoja Marho gets printed in the terminal.
With that in mind, writing decorators with classes becomes incredibly easy, so let’s see how that looks.
class Decorator:
def __init__(self, fn):
self.fn = fn def __call__(self, *args, **kwargs):
new_value = self.fn(*args, **kwargs)
return new_value + '2'@Decorator
def run(name):
value = 'run' + name
print(value)
return value
Adding Parameters To Decorators
So far in the last two articles we have observed how to write decorator functions and classes. In this portion of this write-up, we would be looking to extend that by creating decorator which accept arguments and use that in the decorating of functions, a pattern you will see often if you use or have used python’s Flask micro-framework.
from flask import Flask
app = Flask(__name__)
@app.route('/') #an example of a decorator which accepts arguments
def hello_world():
return 'Hello, World!'
So how do we accomplish this in our code base
Well that’s fairly easy to do, it just requires an extra layer of function.
How does it look with decorator functions
If you don’t know what decorator functions are check out my previous article on them.
def decorator_factory(argument):
def decorator(function):
def wrapper(*args, **kwargs):
funny_stuff()
# something_with_argument(argument)
result = function(*args, **kwargs)
#some extra things you intend to implement
return result
return wrapper
return decorator@decorator_factory(argument)
def other_function(args):
#your function code goes here
How does it look with decorator classes.
import functoolsclass MyDecorator(object):
def __init__(self, argument):
self.arg = argument
def __call__(self, fn):
@functools.wraps(fn)
def decorated(*args, **kwargs):
print("In my decorator before call, with arg %s"self.arg)
result = fn(*args, **kwargs)
print("In my decorator after call, with arg %s" % self.arg)
return result
return decorated
@MyDecorator(argument)
def other_function(args):
#your code goes here
return
Notice that the __call__ function is now nested, resembling a typical decorator function.
So in the next post — the last part on decorators — we will be looking at decorators for classes, how to alter class instances using decorator functions and decorator classes. See you there!