Fundamentals 20 min read

Master Python Decorators: From Basics to Advanced Patterns

This article walks Python developers through the concept of decorators, showing simple timing examples, how to handle functions with parameters and return values, advanced techniques like parameterized, class, stateful, and nested decorators, and provides ready-to-use code templates for real‑world use.

Python Crawling & Data Mining
Python Crawling & Data Mining
Python Crawling & Data Mining
Master Python Decorators: From Basics to Advanced Patterns

Practical Introduction

For Python learners who have passed the beginner stage, decorators become essential. They are used in web development, logging, performance monitoring, permission control, and are a common interview topic.

Problem Statement

Consider the following code:

def step1():
    print('step1.......')

def step2():
    print('step2......')

def step3():
    print('step3......')

step1()
step2()
step3()

The functions run sequentially, but we want to measure the execution time of each.

Brute‑Force Solution

Record start time at the beginning of the function.

Record end time after the business logic.

Subtract to obtain the duration.

Adding timing code to every function quickly becomes cumbersome.

Using a Decorator

A cleaner solution is to create a decorator that wraps the original function.

import time

def timer(func):
    '''Decorator that measures function execution time.'''
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        used = end - start
        print(f'{func.__name__} used {used}')
        return wrapper

The timer function is a decorator that adds timing without modifying the original functions.

Its parameter is the function to be decorated.

It returns a new wrapper function.

The wrapper records start time, calls the original function, records end time, and prints the duration.

Applying the decorator:

@timer
def step1():
    print('step1.......')

@timer
def step2():
    print('step2......')

@timer
def step3():
    print('step3......')

step1()
step2()
step3()

Decorator Syntax Sugar

Using the @ symbol before a function automatically applies the decorator, eliminating the need to modify call sites.

import time

def timer(func):
    '''Decorator that measures function execution time.'''
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        used = end - start
        print(f'{func.__name__} used {used}')
        return wrapper

@timer
def step1():
    print('step1.......')

@timer
def step2():
    print('step2......')

@timer
def step3():
    print('step3......')

step1()
step2()
step3()

Advanced Usage

Functions with Parameters

When decorating a function that accepts arguments, the wrapper must forward them:

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        used = end - start
        print(f'{func.__name__} used {used}')
        return wrapper

This resolves the

TypeError: wrapper() takes 0 positional arguments but 1 was given

error.

Functions with Return Values

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        ret_value = func(*args, **kwargs)
        end = time.time()
        used = end - start
        print(f'{func.__name__} used {used}')
        return ret_value
    return wrapper

Further Topics

Class decorators (decorating a class’s __init__).

Multiple decorators stacked together.

Parameterised decorators (decorators that accept their own arguments).

Stateful decorators that maintain internal state.

Built‑in Decorators

Python provides several built‑in decorators such as @classmethod, @staticmethod, and @property. Example:

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value >= 0:
            self._radius = value
        else:
            raise ValueError('Radius must be positive')

    @property
    def area(self):
        return self.pi() * self.radius ** 2

    @classmethod
    def unit_circle(cls):
        return cls(1)

    @staticmethod
    def pi():
        return 3.1415926535

Creating Custom Decorators

Debug decorator that prints arguments and return value:

def debug(func):
    def wrapper_debug(*args, **kwargs):
        print(f'{func.__name__}: {args}, {kwargs}')
        ret_val = func(*args, **kwargs)
        print(f'return: {ret_val}')
        return ret_val
    return wrapper_debug

Slow decorator that adds a delay:

def slow(func):
    def wrapper_slow(*args, **kwargs):
        print(f'{func.__name__} sleeping 1 second')
        time.sleep(1)
        return func(*args, **kwargs)
    return wrapper_slow

Parameterised Decorator Example

def slow(seconds):
    def decorator_slow(func):
        def wrapper_slow(*args, **kwargs):
            print(f'{func.__name__} sleeping {seconds} second')
            time.sleep(seconds)
            return func(*args, **kwargs)
        return wrapper_slow
    return decorator_slow

@slow(2)
def add(a, b):
    return a + b

Singleton Class Decorator

def singleton(cls):
    '''Create a singleton class.'''
    def single_wrapper(*args, **kwargs):
        if not single_wrapper.instance:
            single_wrapper.instance = cls(*args, **kwargs)
        return single_wrapper.instance
    single_wrapper.instance = None
    return single_wrapper

@singleton
class Counter:
    def __init__(self):
        self._count = 0
    def visit(self):
        self._count += 1
        print(f'visiting: {self._count}')

All instances of Counter share the same state.

Stateful Decorator Example

def count(func):
    def wrapper_count():
        wrapper_count.count += 1
        print(f'{func.__name__}: call #{wrapper_count.count}')
        func()
    wrapper_count.count = 0
    return wrapper_count

Nested Decorators

def timer(func):
    def wrapper():
        start = time.perf_counter()
        func()
        end = time.perf_counter()
        print(f'{func.__name__} used {end - start}')
    return wrapper

@slow
@timer
def run():
    print('Running')

The function run first sleeps (via slow) then measures execution time (via timer).

Conclusion

Understanding decorators—from simple timing to parameterised, class‑level, stateful, and nested usage—empowers Python developers to write cleaner, more reusable code and ace interview questions that frequently involve this powerful feature.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

programmingDecoratorcodeAdvanced
Python Crawling & Data Mining
Written by

Python Crawling & Data Mining

Life's short, I code in Python. This channel shares Python web crawling, data mining, analysis, processing, visualization, automated testing, DevOps, big data, AI, cloud computing, machine learning tools, resources, news, technical articles, tutorial videos and learning materials. Join us!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.