Python Class-Based Decorators
A class decorator is a decorator built from a class.
Call an object like a function
You call a function with round brackets. The __call__ method lets an object answer those brackets.
class Greeter:
def __call__(self, name):
return "Hello, " + name
greeter = Greeter()
print(greeter("Ada"))Count calls with memory
The count lives on the decorator object. Each call through __call__ adds one to it.
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args):
self.count += 1
return self.func(*args)
@CountCalls
def say_hello(name):
return "Hello, " + name
print(say_hello("Ada"))
print(say_hello("Ben"))
print(say_hello.count)Decorate a class itself
A decorator can receive a class, not a function. This one adds a greet method, then returns the changed class.
def add_greet(cls):
def greet(self):
return "Hi, I am " + self.name
cls.greet = greet
return cls
@add_greet
class Person:
def __init__(self, name):
self.name = name
person = Person("Mia")
print(person.greet())An idea in a new place
This shape appeared with dataclasses and properties. Here @dataclass receives the whole class and returns a finished one.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
print(Point(2, 3))Example
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
return self.func(*args, **kwargs)
@CountCalls
def add(a, b):
return a + b
@CountCalls
def greet(name):
return "Hello, " + name
print(add(2, 3))
print(add(10, 20))
print(greet("Ada"))
print("add calls:", add.count)
print("greet calls:", greet.count)Key points
- __call__: the method that lets an object act like a function
- Stateful: a decorator is stateful when it remembers data between calls
- Use a class as a decorator when the wrapper needs its own memory
- Use a decorator on a class to change the class before use
- Dataclass: the @dataclass decorator builds a class from its fields
- Property: the @property decorator makes a method read like an attribute
- The decorator itself is an object built from a class and keeps its own memory.
In this example, CountCalls decorates two functions. Each decorated name holds its own CountCalls object. So add and greet keep separate counts. The __call__ method adds one to count on each call. It then runs the stored function and returns its result. add runs twice, so its count is 2. greet runs once, so its count is 1. The last two lines read the counts back.
Note
After a class decorates a function, the function name holds a decorator object, not the plain function.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Python Playground
Exercise
Why does a class fit a counting decorator well?