In this post, we’ll see what Decorators are, look at some built-in decorators, and see how to create our own custom decorator.
But first of all, what is a Decorator?
“A decorator is a design pattern in Python that allows us to add new functionality to our existing object without modifying its structure. Decorators are implemented as functions that take another function as an argument, extend its behavior, and return a new function with the extended behavior.“
In a nutshell, a decorator wraps another function and allows us to execute code before and after the wrapped function runs, without modifying the function itself.
Python comes with several built-in decorators that are commonly used in object-oriented programming and some of them are:
@staticmethod – It turns a method into a static method, which means it doesn’t receive the implicit self argument.
[MATHOPERATION.PY]
1 2 3 4 5 6 7 | class MathOperation: @staticmethod def sum (a, b): return a + b def multiplication( self , a, b): return a * b |
[MAIN.PY]
1 2 3 4 5 6 7 | from MathOperation import MathOperation objMath = MathOperation() print (f "The result of 5*5 is {objMath.multiplication(5,5)}" ) print (f "The result of 5+5 is {MathOperation.sum(5,5)}" ) |

@classmethod – It is used to define a method that’s bound to the class and not the instance of the class.
One of the most common uses, is to create instance of a class using different types of data.
[PERSON.PY]
1 2 3 4 5 6 7 8 9 10 11 12 | import datetime class Person: def __init__( self , name, age): self .name = name self .age = age @classmethod def from_birth_year( cls , name, birth_year): current_year = datetime.datetime.now().year age = current_year - birth_year return cls (name, age) |
[MAIN.PY]
1 2 3 4 5 6 7 | from Person import Person objPerson = Person( "Damiano1" , 33 ) objPerson2 = Person.from_birth_year( "Damiano2" , 1991 ) print (objPerson.age) print (objPerson2.age) |

@property – It allows us to define methods in a class that can be accessed like attributes.
Moreover, it allows us to add logic when accessing or setting an attribute without changing the external interface of our class.
[RECTANGLE.PY]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class Rectangle: def __init__( self , width, height): self .width = width # Calls the width setter self .height = height # Calls the height setter @property def width( self ): return self ._width @width .setter def width( self , value): if value < = 0 : raise ValueError( "Width must be positive." ) self ._width = value @property def height( self ): return self ._height @height .setter def height( self , value): if value < = 0 : raise ValueError( "Height must be positive." ) self ._height = value @property def area( self ): return self .width * self .height |
[MAIN.PY]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from Rectangle import Rectangle objRect = Rectangle( 5 , 10 ) print ( f "The area of the Rectangle ({objRect.width} - {objRect.height}) is :{objRect.area}" ) objRect.width = 7 print ( f "The area of the Rectangle ({objRect.width} - {objRect.height}) is :{objRect.area}" ) objRect.width = - 7 print ( f "The area of the Rectangle ({objRect.width} - {objRect.height}) is :{objRect.area}" ) |

@abstractmethod – This decorator from the “abc” module indicates that a method must be overridden in a subclass, effectively making the class abstract.
[VEHICLE.PY]
1 2 3 4 5 6 7 | from abc import ABC, abstractmethod class Vehicle(ABC): @abstractmethod def start_engine( self ): pass |
[CARFERRARI.PY]
1 2 3 4 5 6 | from Vehicle import Vehicle class CarFerrari(Vehicle): def start_engine( self ): print ( "Start from Ferrari engine" ) |
[CARFORD.PY]
1 2 3 4 5 6 | from Vehicle import Vehicle class CarFord(Vehicle): def start_engine( self ): print ( "Start from Ford engine" ) |
[MAIN.PY]
1 2 3 4 5 6 7 8 | from CarFerrari import CarFerrari from CarFord import CarFord carFerrari = CarFerrari() carFord = CarFord() carFerrari.start_engine() carFord.start_engine() |

@dataclass – It automatically adds special methods like __init()__ to the class.
[EMPLOYEE.PY]
1 2 3 4 5 6 7 8 9 10 11 12 | from dataclasses import dataclass @dataclass class Employee: name: str position: str salary: float def print_info( self ): print ( f "Employee info: name='{self.name}', position='{self.position}', salary={self.salary}" ) |
[MAIN.PY]
1 2 3 4 5 | from Employee import Employee objEmp = Employee( 'Damiano' , 'Director' , 120000 ) objEmp.print_info() |

CUSTOM DECORATOR – Finally, we will see how to create a custom Decorator called @timer, to calculate and display the time a function takes to execute.
[TIMER.PY]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import time # Import the time module to work with time-related functions def timer(func): """Decorator that measures the execution time of a function.""" def wrapper( * args, * * kwargs): # Record the start time before the function execution start_time = time.time() # Call the original function with all its arguments result = func( * args, * * kwargs) # Record the end time after the function execution end_time = time.time() # Calculate the elapsed time and print it print (f "{func.__name__} took {end_time - start_time:.6f} seconds to execute." ) # Return the result obtained from the original function return result # Return the wrapper function to replace the original function return wrapper |
[MAIN.PY]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from timer import timer # Import the 'timer' decorator from the 'timer' module @timer # Apply the 'timer' decorator to the 'compute_sum' function def compute_sum(n): """Compute the sum of numbers from 0 to n-1.""" total = sum ( range (n)) # Calculate the sum of numbers from 0 to n-1 return total # Return the computed total # Usage result = compute_sum( 1000000 ) # Call 'compute_sum' with n=1,000,000 print (result) # Print the result to the console |
