Fundamentals 5 min read

Master Python Decorators: Simple Steps to Enhance Functions

This tutorial explains the principle and practical usage of Python decorators, guiding you through basic examples, logging decorators, argument‑accepting decorators, and class‑based decorators, while illustrating how they wrap functions to add pre‑ and post‑execution behavior.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Master Python Decorators: Simple Steps to Enhance Functions

This article introduces the principle and usage of Python decorators in an easy‑to‑understand way.

We start with a simple function:

<code>def foo():
    print("do something.")
</code>

To add functionality before and after the call (e.g., logging), we can wrap the function with another function. In Python this is straightforward using a nested function:

<code>def outer(func):
    def inner():
        print("before execution.")
        func()
        print("after execution.")
    return inner
</code>

Usage:

<code>if __name__ == "__main__":
    proxy = outer(foo)
    proxy()
</code>

Output:

<code>before execution.
 do something.
after execution.
</code>

Because everything in Python is an object, the function foo is passed as an argument to outer , which returns inner . The variable proxy therefore references inner , and calling proxy() executes the three statements inside inner .

Python provides decorator syntax sugar using the @decorator_name notation, making the pattern even simpler:

<code>def outer(func):
    def inner():
        # same as above
        pass
    return inner

@outer
def foo():
    print("do something.")

if __name__ == "__main__":
    foo()
</code>

The @outer decorator tells Python to pass foo to outer when foo is called, thereby enhancing its behavior.

A more practical example is a logger decorator that records the call time:

<code>from datetime import datetime

def logger(func):
    def wrapper(*args, **kwargs):
        print('[INFO] {}, function "{}" was called '.format(datetime.now(), func.__name__))
        return func(*args, **kwargs)
    return wrapper

@logger
def foo():
    print("do something.")

if __name__ == "__main__":
    foo()
</code>

If a decorator needs its own arguments, an extra level of nesting is required:

<code>def logger(msg):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print('[INFO] {}, "{}" was called with message "{}"'.format(datetime.now(), func.__name__, msg))
            return func(*args, **kwargs)
        return wrapper
    return decorator

@logger("Maybe bored.")
def foo(name):
    print("do something, " + name)

foo('Johnny')
</code>

Decorators can also be implemented as classes by defining __init__ and __call__ methods:

<code>from datetime import datetime

class logger(object):
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, **kwargs):
        print('[INFO] {}, function "{}" was called '.format(datetime.now(), self.func.__name__))
        return self.func(*args, **kwargs)

@logger
def foo():
    print("do something.")

if __name__ == "__main__":
    foo()
</code>

These examples illustrate how decorators can be used to inject cross‑cutting concerns such as logging, timing, or access control without modifying the original function code.

pythonLoggingCode ExampledecoratorFunction Wrapping
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

login 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.