Welcome to the 2nd part of my python decorator series. In my first post, I introduced you to decorators.
In this post, I am going to show you how to write class decorators and decorate class methods.
OUTLINE OF DECORATOR SERIES
i. Introduction to decorators and decorating python functions.
ii.Writing class decorators and decorating class methods (this article)
iii. Decorators that take arguments.
Defining Decorators as classes
To define a class decorator, we need to make sure it implements the__call__() and __get__() method.
The __call__
method enables us to write classes where the instances behave like functions and can be called like a function.
The __get__
method is used to get the attribute of the owner class or instance of that class.
Now let’s write our class decorator
Example.
Here, we’ll implement a decorator that checks if all the characters in the user name are alphabetic and if they are and capitalize the first letter.
import functools import types class StringFormatter: def __init__(self, func): self.func = func # functools.wraps for classes... functools.update_wrapper(self, func) def __call__(self, *args, **kwargs): function_result = self.func(*args, **kwargs) if function_result.isalpha(): return function_result.capitalize() else: return "incorrect text format" def __get__(self, instance,cls): if instance is None: return self else: return types.MethodType(self, instance)
The class decorator above can be used either with a python function or a class.
The only notable difference between functions and classes is that functools.wraps
is now replaced with functools.update_wrapper
in the __init__ method.
@StringFormatter def input_name(first_name): return first_name class UserRegistration: @StringFormatter def print_name(self,name): return name print(input_name("GH567")) print(input_name("linda")) user = UserRegistration() user.print_name("thomas")
'incorrect text format' 'Linda' 'Thomas'
Decorating class functions (methods)
Decorating class functions is very similar to regular functions, but you need to be aware of the required first argument, self— the class instance.
We would test our first class decorator (StringFormatter) and also create a new function decorator to test our class methods.
Design a simple decorator that checks if the specified user age is of the specified type and returns the output.
def check_type(function): @functools.wraps(function) def wrapper( *args,**kwargs): result = function(*args,**kwargs) if isinstance(result, int): return result else: return "Your age is incorrect" return wrapper
class Person: def __init__(self, name, age): self.name = name self.age = age @check_type def input_age(self): return self.age @StringFormatter def input_name(self): return self.name person = Person("john", 56) print(person.input_age()) print(person.input_name())
56 John
Applying Decorators to Class and Static Methods
The difference between a @classmethod and a @staticmethod is fairly simple. The classmethod passes a class object instead of a class instance (self), and staticmethod skips both the class and the instance entirely. This effectively makes staticmethod very similar to a regular function outside of a class.
I recently wrote an article on decoding the staticmethod and classmethod, please check it here.
Applying decorators to class and static methods is straightforward, but make sure that your decorators are applied before @classmethod or @staticmethod because If you get the order of decorators wrong, you’ll get an error
For example: we have a decorator called @deco
def deco(function): return function class A: @deco def instance_method(self): pass @classmethod @deco def class_method(cls): pass @staticmethod @deco def static_method(): pass
Let us design a simple application to get started and apply our already created @check_type decorator.
from datetime import datetime as dt class User: def __init__(self, name, age): self.name = name self.age = age @staticmethod @check_type def deduce_birthday(name, year): age= dt.now().year - year return age @classmethod @check_type def compute_age_diff(cls,name, year): age= dt.now().year - year return age user_1 = User.compute_age_diff("Jerry", 1998) print(user_1) user_2 = User.compute_age_diff("Jerry", 1209.98) print(user_2)
22 Your age is incorrect
You can see clearly the decorator (@checktype checks if the age is an int) because our age was 810.02 and is a float type object, our output was “Your age is incorrect”
user_3 = User.deduce_birthday("Jerry", 1998) print(user_3) user_4 = User.deduce_birthday("Jerry", 1209.98) print(user_4)
22 Your age is incorrect
In my next post, we look at creating decorators that take arguments
Thanks for reading, please share with your network, if you found this post helpful, comment, and share your thoughts in the comment section.