Introduction to Python Decorators: Extending Functionality with the @ Syntax

Python Decorators allow you to add specific processing before or after the execution of a function without changing the function’s code directly. They are very useful when implementing processing that you want to apply to multiple functions (cross-cutting concerns), such as outputting logs, measuring execution time, or checking access permissions.

This article explains the mechanism of decorators, how to implement them using the @ syntax, and how to write practical decorators that handle arguments and return values.


目次

Basic Concept of Decorators

A decorator is essentially a “higher-order function that takes a function as an argument and returns a new function (the original function with added processing).” First, let’s look at how a decorator works in code without using the @ syntax.

# 1. Decorator Function
def simple_logger(func):
    """
    Function that adds log output before and after the received function.
    """
    # 2. Define a new function (wrapper function) inside
    def wrapper():
        print("--- Process Start ---")
        func() # Execute the original function
        print("--- Process End ---")
    
    # 3. Return the new function
    return wrapper

# Function to be decorated
def greet():
    print("Hello!")

# --- Applying the Decorator ---

# Pass the greet function to simple_logger and receive the new function
decorated_greet = simple_logger(greet)

# Execute the new function
decorated_greet()

Output:

--- Process Start ---
Hello!
--- Process End ---

We added “Process Start” and “Process End” logs without rewriting the greet function code at all. This is the basic mechanism of a decorator.


Writing with the @ Syntax

In Python, by writing @DecoratorName immediately before the function definition, you can automatically perform the “function replacement” described above. This is called “Syntactic Sugar.”

# Decorator definition (same as before)
def simple_logger(func):
    def wrapper():
        print("--- Process Start ---")
        func()
        print("--- Process End ---")
    return wrapper

# Apply decorator using @ syntax
@simple_logger
def say_bye():
    print("Goodbye!")

# Just calling it executes the decorated function
say_bye()

Output:

--- Process Start ---
Goodbye!
--- Process End ---

Writing @simple_logger is equivalent to executing say_bye = simple_logger(say_bye).


General-Purpose Decorators Handling Arguments and Return Values

The wrapper function in the previous example did not accept arguments, so it would cause an error if applied to a function that requires arguments. Also, if the original function returned a value, it would be lost because the wrapper didn’t return it. To create a general-purpose decorator that works with any function, we use variable-length arguments (*args, **kwargs) and return.

Practical Code Example: Logging Execution Results

Here is a decorator that displays the function’s arguments and its execution result (return value).

def result_logger(func):
    """
    Decorator to log function arguments and return value.
    """
    # Accept any arguments with *args, **kwargs
    def wrapper(*args, **kwargs):
        print(f"[Log] Function '{func.__name__}' called.")
        
        # Execute the original function and store the return value
        result = func(*args, **kwargs)
        
        print(f"[Log] Return Value: {result}")
        
        # Return the result to the caller
        return result
    
    return wrapper

# --- Using the Decorator ---

@result_logger
def add_numbers(a, b):
    return a + b

@result_logger
def multiply(x, y):
    return x * y

# Execution
sum_val = add_numbers(10, 20)
print(f"Sum: {sum_val}\n")

product_val = multiply(5, 4)
print(f"Product: {product_val}")

Output:

[Log] Function 'add_numbers' called.
[Log] Return Value: 30
Sum: 30

[Log] Function 'multiply' called.
[Log] Return Value: 20
Product: 20

Explanation

  1. def wrapper(*args, **kwargs):: Defined to accept any arguments.
  2. result = func(*args, **kwargs): Passes the received arguments exactly as they are to the original function.
  3. return result: Returns the result of the original function as the return value of the wrapper function. If you forget this, the decorated function will always return None.

Summary

  • A decorator is a mechanism to wrap a function and add functionality.
  • You call the original function inside def wrapper() and return that wrapper.
  • Using @DecoratorName allows for concise application.
  • To support functions with arguments and return values, the standard practice is to accept *args, **kwargs in the wrapper function and use return at the end.
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次