🎓 A1Learner
🔍

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?