Meta-programming in Python: Decorator Classes

Marho Onothoja
3 min readMar 13, 2021

--

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 name
person = 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!

--

--

Marho Onothoja
Marho Onothoja

No responses yet