Decorator in Python

Introduction

You might have seen @something before a function in Python and ask yourself, what does that mean? These @somethings are called decorators. We use a decorator to modify a function without changing it.

In this tutorial, we define a decorator using a function and a class also with give an example on stacking 3 decorators.

Decorator with function


from typing import Callable


def check_triangle_validation(func: Callable[[float, float, float], float]) -> Callable[[float, float, float], float]:
    def wrapper(a: float, b: float, c: float) -> float:
        if (a + b <= c) or (b + c <= a) or (a + c <= b):
            raise Exception('Not a valid triangle')
        result = func(a, b, c)
        return result

    return wrapper


@check_triangle_validation
def calculate_triangle_area(a: float, b: float, c: float) -> float:
    return a + b + c


def main():
    a = 2
    b = 2
    c = 3
    result = calculate_triangle_area(a, b, c)
    print(result)


if __name__ == '__main__':
    main()

In this example, we have a function that calculates the area of a triangle (calculate_triangle_area). We want to make sure that the given triangle is a valid triangle. To do so, we define a decorator called check_triangle_validation that gets a function as an argument and returns a wrapper. Then we have an inner function that is going to run instead of the decorated function that we call it wrapper. in wrapper, we check if every vertex is less than the sum of the other two. Then we execute the decorated function and finally, we return the result.

If we run our function with values of a=2,b=2,c=3, we are going to have 7 as a result. But if we run our function with values of a=1,b=1,c=3, we are getting an Exception that we defined in the if statement in our wrapper.

Decorator with class

from typing import Callable


class CheckTriangleValidation:

    def __init__(self, func: Callable[[float, float, float], float]):
        self.func = func

    def __call__(self, a: float, b: float, c: float) -> float:
        if (a + b <= c) or (b + c <= a) or (a + c <= b):
            raise Exception('Not a valid triangle')
        result = self.func(a, b, c)
        return result


@CheckTriangleValidation
def calculate_triangle_area(a: float, b: float, c: float) -> float:
    return a + b + c


def main():
    a = 2
    b = 2
    c = 3
    result = calculate_triangle_area(a, b, c)
    print(result)


if __name__ == '__main__':
    main()

Defining a decorator using a class is pretty much the same as the function. we get our decorated function as an argument in __init__ function. Then we should define a __call__ function that works exactly like our wrapper.

Stacking decorators


from typing import Callable


def change_meter_to_feet(func: Callable[[float, float, float], float]) -> Callable[[float, float, float], float]:
    def wrapper(a: float, b: float, c: float) -> float:
        result = func(a, b, c)
        return result * 3.28084

    return wrapper


def check_positive_vertices(func: Callable[[float, float, float], float]):
    def wrapper(a: float, b: float, c: float) -> float:
        if a <= 0 or b <= 0 or c <= 0:
            raise Exception('Vertices should be natural numbers')
        result = func(a, b, c)
        return result

    return wrapper


def check_valid_vertices(func: Callable[[float, float, float], float]):
    def wrapper(a: float, b: float, c: float) -> float:
        if (a + b <= c) or (b + c <= a) or (a + c <= b):
            raise Exception('Not a valid triangle')
        result = func(a, b, c)
        return result

    return wrapper


@check_positive_vertices
@check_valid_vertices
@change_meter_to_feet
def calculate_triangle_area(a: float, b: float, c: float) -> float:
    return a + b + c


def main():
    a = 2
    b = 2
    c = 3
    result = calculate_triangle_area(a, b, c)
    print(result)


if __name__ == '__main__':
    main()

We can stack up multiple decorators. In this example, we have three decorators. check_valid_triangle checks if every vertex is less than the other two. check_positive_vertices check if all vertices are positive. change_meter_to_feet changes the calculated result from meter to feet.

If we run calculate_trinalge_area with values of a=2, b=2, c=3, the result will be 22.96588.

© 2025 LiterallyTheOne