Class Decorators in Python (56/100 Days of Python)

Martin Mirakyan
2 min readFeb 26, 2023

--

Day 56 of the “100 Days of Python” blog post series covering class decorators in Python

Besides using decorators to modify function behaviors, in Python, decorators can also be used to modify the behavior of classes. Class decorators are functions that take a class as an argument, modify the class in some way, and then return the modified class.

Class decorators can be used to add new attributes or methods to a class, modify existing attributes or methods, or even replace the entire class with a new implementation. In this tutorial, we’ll cover some common use cases for class decorators and how to create your own.

Adding New Attributes Or Methods To a Class

One common use case for class decorators is to add new attributes or methods to a class. For example, you might want to add a new class-level attribute that keeps track of the number of instances created, or add a new method that provides some additional functionality:

def add_class_counter(cls):
cls.num_instances = 0
orig_init = cls.__init__
def new_init(self, *args, **kwargs):
cls.num_instances += 1
orig_init(self, *args, **kwargs)
cls.__init__ = new_init
return cls

In this example, add_class_counter is a class decorator that takes a class cls as an argument. The decorator adds a new class-level attribute num_instances and a new __init__ method that increments the num_instances attribute every time a new instance is created.

You can apply this decorator to a class by adding the decorator at the top of a class:

@add_class_counter
class MyClass:
def __init__(self, name):
self.name = name

Now, every time you create a new instance of MyClass, the num_instances attribute is incremented:

a = MyClass('Alice')
b = MyClass('Bob')
print(MyClass.num_instances) # 2

Modifying Existing Attributes Or Methods Of a Class

Another use case for class decorators is to modify existing attributes or methods of a class. For instance, you might want to add some additional behavior to an existing method or change the behavior of an existing attribute:

def add_logging_to_method(method):
def new_method(self, *args, **kwargs):
print(f'Calling method {method.__name__}')
return method(self, *args, **kwargs)
return new_method


def add_logging_to_all_methods(cls):
for name, method in vars(cls).items():
if callable(method):
setattr(cls, name, add_logging_to_method(method))
return cls

In this example, add_logging_to_method is a function that takes a method as an argument and returns a new method that logs information about the method call before calling the original method. add_logging_to_all_methods is a class decorator that modifies all methods of a class by applying the add_logging_to_method decorator to each one.

@add_logging_to_all_methods
class MyClass:
def __init__(self, name):
self.name = name
def say_hello(self):
print(f'Hello, {self.name}!')

Now, every time you call the say_hello method, information about the method call is logged:

a = MyClass('Alice')
a.say_hello()
# Calling method say_hello
# Hello, Alice!

What’s next?

--

--