Fundamentals 11 min read

Comprehensive Guide to Python Decorators: From Basic to Advanced Usage

This article provides a thorough tutorial on Python decorators, covering simple decorators without parameters, decorators with optional arguments, class‑based decorators, and advanced techniques such as preserving function metadata with functools.wraps, illustrating each concept with clear code examples and explanations.

NetEase Game Operations Platform
NetEase Game Operations Platform
NetEase Game Operations Platform
Comprehensive Guide to Python Decorators: From Basic to Advanced Usage

Python decorators are a frequent interview topic because their syntax can be complex and beginners often get confused; this article explains various usages of decorators so you can understand them completely.

Decorator without parameters

We first write a simple decorator that records the function's execution before and after it runs.

def dec1(func):
    print(func)
    def _wrap():
        print('before run')
        r = func()
        print('after run')
        return r
    return _wrap

@dec1
def f1():
    print('call f1')

Running the above shows that the decorator runs before the function is actually called because @dec1 is equivalent to a separate statement dec1(f1) .

f1(1)

Output:

before run
call f1
after run

Decorator without parameters but with empty parentheses

Calling the decorator with empty parentheses raises an error because the original decorator expects a function argument.

Traceback (most recent call last)
  ...
TypeError: dec1() missing 1 required positional argument: 'func'

We modify the decorator to accept no arguments and return a wrapper.

def dec2():
    def _wrap(func):
        print(func)
        print('before run')
        return func
    return _wrap

@dec2()
def f2():
    print('call f2')

f2()

Output shows the expected behavior:

<function f2 at 0x...>
before run
call f2

Decorator with parameters, function without parameters

To add parameters to a decorator, we wrap the original function inside another function that captures the decorator arguments.

def dec3(func):
    print(func)
    def _wrap(param):
        print(param)
        r = func(param)
        return r
    return _wrap

@dec3
def f3(a):
    print('call f3', a)

f3(1)

Result:

<function f3 at 0x...>
1
call f3 1

Decorator with parameters, function with parameters

Here the decorator receives an integer and adds it to the function's argument.

def dec4_w(d_param):
    print(d_param)
    def dec4(func):
        print(func)
        def _wrap(param):
            print(param)
            r = func(param + d_param)
            return r
        return _wrap
    return dec4

@dec4_w(2)
def f4(a):
    print('call f4', a)

f4(1)

Output:

2
<function f4 at 0x...>
1
call f4 3

Decorator with multiple parameters

Extending the previous pattern to accept two decorator arguments.

def dec5_w(d_param_1, d_param_2):
    print(d_param_1, d_param_2)
    def dec5(func):
        print(func)
        def _wrap(param):
            print(param)
            r = func(param + d_param_1 + d_param_2)
            return r
        return _wrap
    return dec5

@dec5_w(2, 3)
def f5(a):
    print('call f5', a)

f5(1)

Output:

2 3
<function f5 at 0x...>
1
call f5 6

Using *args for variable‑length decorator arguments

def dec6_w(*args):
    d_param_1, d_param_2 = args
    print(d_param_1, d_param_2)
    def dec6(func):
        print(func)
        def _wrap(*args):
            param = args[0]
            print(param)
            r = func(param + d_param_1 + d_param_2)
            return r
        return _wrap
    return dec6

@dec6_w(2, 3)
def f6(a):
    print('call f6', a)

f6(1)
print(f6.__name__)

Result demonstrates that the wrapper name is _wrap unless we use functools.wraps :

2 3
<function f6 at 0x...>
1
call f6 6
_wrap

Preserving metadata with functools.wraps

from functools import wraps

def dec7_w(*args):
    d_param_1, d_param_2 = args
    print(d_param_1, d_param_2)
    def dec7(func):
        print(func)
        @wraps(func)
        def _wrap(*args):
            param = args[0]
            print(param)
            r = func(param + d_param_1 + d_param_2)
            return r
        return _wrap
    return dec7

@dec7_w(2, 3)
def f7(a):
    print('call f7', a)

f7(1)
print(f7.__name__)

Now the function name remains f7 .

Decorator implemented as a class (parameters + function)

from functools import wraps

class dec8_c:
    def __init__(self, *args):
        self.d_param_1, self.d_param_2 = args
        print(self.d_param_1, self.d_param_2)
    def __call__(self, func):
        print(func)
        @wraps(func)
        def _wrap(param):
            print(param)
            r = func(param + self.d_param_1 + self.d_param_2)
            return r
        return _wrap

@dec8_c(2, 3)
def f8(a):
    print('call f8', a)

f8(1)
print(f8.__name__)

The class‑based decorator works like the function version but uses __call__ to apply the wrapper.

Decorator class without parameters

class dec9_c:
    def __init__(self, func):
        print(func)
        self.func = func
        self.__name__ = func.__name__
    def __call__(self, param):
        print(param)
        r = self.func(param)
        return r

@dec9_c
def f9(a):
    print('call f9', a)

f9(1)
print(f9.__name__)

This pattern stores the original function in the instance and allows the decorated function to retain its name.

Using __new__ to avoid defining __call__

from functools import wraps

class dec9x_c:
    def __new__(self, func):
        print(func)
        @wraps(func)
        def dec9x(param):
            print(param)
            r = func(param)
            return r
        return dec9x

@dec9x_c
def f9x(a):
    print('call f9x', a)

f9x(1)
print(f9x.__name__)

Using __new__ returns the wrapper directly, eliminating the need for a separate __call__ method.

Overall, the article demonstrates that decorators can be implemented as functions or classes, with or without parameters, and shows how to preserve metadata and handle variable arguments effectively.

PythonprogrammingdecoratorfunctionAdvanced
NetEase Game Operations Platform
Written by

NetEase Game Operations Platform

The NetEase Game Automated Operations Platform delivers stable services for thousands of NetEase titles, focusing on efficient ops workflows, intelligent monitoring, and virtualization.

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.