Article Complete
    AIM

    Step-By-Step Introduction To Python Decorators

    Step-By-Step Introduction To Python Decorators

    Discover Python decorators with our step-by-step guide. Learn how to enhance your functions effortlessly. Start mastering decorators today!

    Step-By-Step Introduction To Python Decorators
    Aditya Singh··Updated DECEMBER 30, 2024, 5:30 AM
    5 min

    Python decorators are extremely useful, albeit a bit hard to wrap your head around. They enable developers to modify or extend a function or class’s functionalities by wrapping it using a single line of code @decorator_name. Before we delve into decorators’ working, let’s understand the concept of first-class functions in Python. In a programming language, first-class objects are entities that have no restrictions on their usage. They can be dynamically created, stored in variables and data structures, passed as a function argument, or returned by a function. Everything in Python is a first-class object, even those “primitive types” in other languages.  Let’s see what this means for functions in Python:

    • Functions can be stored in variables and data structures.
     def square(x):
         print(x*x)
     def cube(x):
         print(x**3)
     def quartic(x):
         print(x**4)
     power_two = square
     power_two(6)
    
     powers = [square, cube, quartic]
     powers[2](5) 
    -----------------------------Output-----------------------------
     36
     625 
    • They can be passed as an argument, returned by another function or be nested inside another function.
     def deocrator_function(func):
         '''A function that accepts another function '''
         def wrapper():
             print("wrapping")
             func()
             print("done")
         return wrapper
    
     def f():
         '''Example function '''
         print("function f called")
     decorator = deocrator_function(f)
     print(decorator)
     decorator() #the inner wrapper function is returned so it needs to called 
    -----------------------------Output-----------------------------
     .wrapper at 0x7f73ecfa0dd0>
     wrapping
     function f called
     done 

    Creating a Decorator

    What we just did with the nested functions precisely what a decorator does but with a simpler syntax.  The function decorator_function is a decorator and can be used to wrap other functions using the @decorator_name syntax. 

     @decorator_function
     def g():
         ''' Yet another useless example function '''
         print("function g called")
     g() 
    -----------------------------Output-----------------------------
     wrapping
     function g called
     done 

    Using @decorator_function invokes the function decorator_function() with g as the argument and calls the returned wrapper() function. 

    Creating a Decorator for Functions with Arguments 

    One issue with decorating functions with arguments is that the number of arguments can vary with the function. This can be easily overcome by using yet another immensely useful offering of Python: *args and **kwargs.

     def decorator_arguments(func):
         def wrapper(*args, **kwargs):
             print("decorating")
             func(*args, **kwargs)
             print("done")
         return wrapper
    
     @decorator_arguments
     def add(a, b, c, d):
         print("Sum is {}".format(a + b + c + d))
     add(10, 54, 13, 34) 
    -----------------------------Output-----------------------------
     decorating
     Sum is 111
     done 

    Decorators with Arguments

    Sometimes the functionality introduced by the decorator will require additional arguments; this can be accommodated by nesting another function. The outermost function is responsible for the decorator argument, the inner functions for the function being decorated and the function arguments, respectively.

     def multiply(*outer_args, **outer_kwargs):
         def inner_function(func):
             def wrapper(*func_args, **func_kwargs):
                 print(f"Times {outer_args[0]} is {outer_args[0] * func(*func_args, **func_kwargs)}")
             return wrapper
         return inner_function
    
     @multiply(99)
     def basically_an_input(n):
         print(f"Input number {n}")
         return n
     basically_an_input(5) 
    -----------------------------Output-----------------------------
     Input number 5
     Times 99 is 495 

    Decorating Classes

    There are two ways of using decorators with classes; one can either decorate the individual methods inside the class or decorate the whole class.  Three of the most common Python decorators are used for decorating class methods, @property is used to create property attributes that can only be accessed through its getter, setter, and deleter methods.  @staticmethod and @classmethod are used to define class methods that are not connected to particular instances of the class. Static methods don’t require an argument, while class methods take the class as an argument. 

     class Account:
         def __init__(self, balance):
             self._balance = balance
         @property
         def balance(self):
             """Gets balance"""
             return self._balance
         @balance.setter
         def balance(self, value):
             """Set balance, raise error if negative"""
             if value >= 0:
                 self._balance = value
             else:
                 raise ValueError("balance must be positive")
         @classmethod
         def new_account(cls):
             """Returns a new account with 100.00 balance"""
             return cls(100.00)
         @staticmethod
         def interest():
             """The interest rate"""
             return 5.25
    
    
     acc = Account(39825.75)
     print(acc.balance)
     acc.balance = 98621.75
     print(acc.balance)
    
     #testing if the setter is being used
     try:
         acc.balance = -354 
     except:
         print("Setter method is being used")
     acc2 = Account.new_account()
     print(acc2.balance)
     print(f"Calling static method using class: {Account.interest()}, using instance {acc.interest()}") 
    -----------------------------Output-----------------------------
     39825.75
     98621.75
     Setter method is being used
     100.0
     Calling static method using class: 5.25, using instance 5.25 

    Now let’s see how one can decorate the whole class.

     import time
     def timer(example):
         def wrapper(*args, **kwargs):
             start = time.perf_counter()
             res = example(*args, **kwargs)
             end = time.perf_counter()
             run_time = end - start
             print("Finished in {} secs".format(run_time))
             return res
         return wrapper
    
     @timer
     class Example:
         def __init__(self, n):
             self.n = n
             time.sleep(n if n < 3 else 2)
             print("Example running")
     x = Example(5) 
    -----------------------------Output-----------------------------
     Example running
     Finished in 2.0035361850023037 secs
     Note that decorating the class does not decorate all of its methods just __init__.   

    Class as Decorator

    Creating decorators as classes is useful in applications where the decorator might need to maintain a state. For making the class a decorator, it needs to be callable; this is achieved using the dunder method __call__. Furthermore, the __init__ method needs to take a function as an argument.

     class CountUpdates:
         def __init__(self, func):
             self.func = func
             self.version = 0
         def __call__(self, *args, **kwargs):
             self.version += 1
             print(f"Updating to version 0.3.{self.version}")
             return self.func(*args, **kwargs)
    
     @CountUpdates
     def update():
         print("Update complete", end ="\n\n")
     update()
     update()
     update()
     update()
     print(update.version) 
    -----------------------------Output-----------------------------
     Updating to version 0.3.1
     Update complete
     Updating to version 0.3.2
     Update complete
     Updating to version 0.3.3
     Update complete
     Updating to version 0.3.4
     Update complete
     4 

    The Colab notebook for the above implementation can be found here. 

    Your reaction

    Discussion

    What's your take on this story?

    0/1000

    No comments yet — be the first to share your view.

    Explore our newsletters

    Build your routine with some of our top newsletters or view them all here.

    Wake up informed

    Make sense of the day's AI news and breakthroughs with our morning briefing.

    Belamy
    WeeklyBelamySee the latest

    Industry intelligence

    Receive a roundup of AI adoption stories by industry vertical, curated for professionals.

    3x WeeklySector6See the latest

    By signing up, you agree to our Privacy Policy.

    Membership

    Stay ahead with AI insights.

    Unlimited access to premium articles, exclusive analysis, and the stories shaping India's AI economy.

    Subscribe now

    AIM Americas

    1190 Miraloma Way Suite P, Sunnyvale, CA 94085

    AIM India

    1st Floor, Sakti Statesman, Bellandur, Bengaluru – 560103

    © 2026 AIM. All rights reserved.