Advanced Python Decorators: Elevating Your Code

Imagine you’re a chef in a bustling kitchen. You have a recipe—a function, if you will. Over time, you find that most of your dishes require a drizzle of olive oil, a pinch of salt, or a sprinkle of herbs before they’re served. Instead of manually adding these finishing touches to every dish, wouldn’t it be convenient to have an assistant who applies them automatically? That’s precisely what Python decorators can do for your code—add functionality in an elegant, reusable, and expressive way.

In this article, we’ll explore the world of advanced Python decorators. We’ll go beyond the basics, diving into parameterized decorators, stackable decorators, and even decorators with classes. We’ll also highlight best practices and pitfalls to avoid. Ready? Let’s start cooking!

The Basics Revisited

Before diving into the deep end, let’s revisit the foundation. A decorator in Python is simply a function that takes another function (or method) as an argument, augments it, and returns a new function. Here’s an example:

<span># Basic decorator example </span><span>def</span> <span>simple_decorator</span><span>(</span><span>func</span><span>):</span>
<span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>print</span><span>(</span><span>f</span><span>"</span><span>Calling </span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span>...</span><span>"</span><span>)</span>
<span>result</span> <span>=</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>print</span><span>(</span><span>f</span><span>"</span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span> finished.</span><span>"</span><span>)</span>
<span>return</span> <span>result</span>
<span>return</span> <span>wrapper</span>
<span>@simple_decorator</span>
<span>def</span> <span>say_hello</span><span>():</span>
<span>print</span><span>(</span><span>"</span><span>Hello, world!</span><span>"</span><span>)</span>
<span>say_hello</span><span>()</span>
<span># Basic decorator example </span><span>def</span> <span>simple_decorator</span><span>(</span><span>func</span><span>):</span>
    <span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>print</span><span>(</span><span>f</span><span>"</span><span>Calling </span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span>...</span><span>"</span><span>)</span>
        <span>result</span> <span>=</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
        <span>print</span><span>(</span><span>f</span><span>"</span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span> finished.</span><span>"</span><span>)</span>
        <span>return</span> <span>result</span>
    <span>return</span> <span>wrapper</span>

<span>@simple_decorator</span>
<span>def</span> <span>say_hello</span><span>():</span>
    <span>print</span><span>(</span><span>"</span><span>Hello, world!</span><span>"</span><span>)</span>

<span>say_hello</span><span>()</span>
# Basic decorator example def simple_decorator(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__}...") result = func(*args, **kwargs) print(f"{func.__name__} finished.") return result return wrapper @simple_decorator def say_hello(): print("Hello, world!") say_hello()

Enter fullscreen mode Exit fullscreen mode

Output:

Calling say_hello...
Hello, world!
say_hello finished.
Calling say_hello...
Hello, world!
say_hello finished.
Calling say_hello... Hello, world! say_hello finished.

Enter fullscreen mode Exit fullscreen mode

Now, let’s graduate to the advanced use cases.

Parameterized Decorators

Sometimes, a decorator needs to accept its own arguments. For instance, what if we want a decorator that logs messages at different levels (INFO, DEBUG, ERROR)?

<span># Parameterized decorator example </span><span>def</span> <span>log</span><span>(</span><span>level</span><span>):</span>
<span>def</span> <span>decorator</span><span>(</span><span>func</span><span>):</span>
<span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>print</span><span>(</span><span>f</span><span>"</span><span>[</span><span>{</span><span>level</span><span>}</span><span>] Calling </span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span>...</span><span>"</span><span>)</span>
<span>result</span> <span>=</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>print</span><span>(</span><span>f</span><span>"</span><span>[</span><span>{</span><span>level</span><span>}</span><span>] </span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span> finished.</span><span>"</span><span>)</span>
<span>return</span> <span>result</span>
<span>return</span> <span>wrapper</span>
<span>return</span> <span>decorator</span>
<span>@log</span><span>(</span><span>"</span><span>INFO</span><span>"</span><span>)</span>
<span>def</span> <span>process_data</span><span>():</span>
<span>print</span><span>(</span><span>"</span><span>Processing data...</span><span>"</span><span>)</span>
<span>process_data</span><span>()</span>
<span># Parameterized decorator example </span><span>def</span> <span>log</span><span>(</span><span>level</span><span>):</span>
    <span>def</span> <span>decorator</span><span>(</span><span>func</span><span>):</span>
        <span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
            <span>print</span><span>(</span><span>f</span><span>"</span><span>[</span><span>{</span><span>level</span><span>}</span><span>] Calling </span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span>...</span><span>"</span><span>)</span>
            <span>result</span> <span>=</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
            <span>print</span><span>(</span><span>f</span><span>"</span><span>[</span><span>{</span><span>level</span><span>}</span><span>] </span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span> finished.</span><span>"</span><span>)</span>
            <span>return</span> <span>result</span>
        <span>return</span> <span>wrapper</span>
    <span>return</span> <span>decorator</span>

<span>@log</span><span>(</span><span>"</span><span>INFO</span><span>"</span><span>)</span>
<span>def</span> <span>process_data</span><span>():</span>
    <span>print</span><span>(</span><span>"</span><span>Processing data...</span><span>"</span><span>)</span>

<span>process_data</span><span>()</span>
# Parameterized decorator example def log(level): def decorator(func): def wrapper(*args, **kwargs): print(f"[{level}] Calling {func.__name__}...") result = func(*args, **kwargs) print(f"[{level}] {func.__name__} finished.") return result return wrapper return decorator @log("INFO") def process_data(): print("Processing data...") process_data()

Enter fullscreen mode Exit fullscreen mode

Output:

[INFO] Calling process_data...
Processing data...
[INFO] process_data finished.
[INFO] Calling process_data...
Processing data...
[INFO] process_data finished.
[INFO] Calling process_data... Processing data... [INFO] process_data finished.

Enter fullscreen mode Exit fullscreen mode

This layered structure—a function returning a decorator—is key to creating flexible, parameterized decorators.

Stackable Decorators

Python allows multiple decorators to be applied to a single function. Let’s create two decorators and stack them.

<span># Stackable decorators </span>
<span>def</span> <span>uppercase</span><span>(</span><span>func</span><span>):</span>
<span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>result</span> <span>=</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>return</span> <span>result</span><span>.</span><span>upper</span><span>()</span>
<span>return</span> <span>wrapper</span>
<span>def</span> <span>exclaim</span><span>(</span><span>func</span><span>):</span>
<span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>result</span> <span>=</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>return</span> <span>result</span> <span>+</span> <span>"</span><span>!!!</span><span>"</span>
<span>return</span> <span>wrapper</span>
<span>@uppercase</span>
<span>@exclaim</span>
<span>def</span> <span>greet</span><span>():</span>
<span>return</span> <span>"</span><span>hello</span><span>"</span>
<span>print</span><span>(</span><span>greet</span><span>())</span>
<span># Stackable decorators </span>
<span>def</span> <span>uppercase</span><span>(</span><span>func</span><span>):</span>
    <span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>result</span> <span>=</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
        <span>return</span> <span>result</span><span>.</span><span>upper</span><span>()</span>
    <span>return</span> <span>wrapper</span>

<span>def</span> <span>exclaim</span><span>(</span><span>func</span><span>):</span>
    <span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>result</span> <span>=</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
        <span>return</span> <span>result</span> <span>+</span> <span>"</span><span>!!!</span><span>"</span>
    <span>return</span> <span>wrapper</span>

<span>@uppercase</span>
<span>@exclaim</span>
<span>def</span> <span>greet</span><span>():</span>
    <span>return</span> <span>"</span><span>hello</span><span>"</span>

<span>print</span><span>(</span><span>greet</span><span>())</span>
# Stackable decorators def uppercase(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result.upper() return wrapper def exclaim(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result + "!!!" return wrapper @uppercase @exclaim def greet(): return "hello" print(greet())

Enter fullscreen mode Exit fullscreen mode

Output:

HELLO!!!
HELLO!!!
HELLO!!!

Enter fullscreen mode Exit fullscreen mode

Here, the decorators are applied in a bottom-up manner: @exclaim wraps greet, and @uppercase wraps the result.

Using Classes as Decorators

A lesser-known feature of Python is that classes can be used as decorators. This can be particularly useful when you need to maintain state.

<span># Class-based decorator </span><span>class</span> <span>CountCalls</span><span>:</span>
<span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>func</span><span>):</span>
<span>self</span><span>.</span><span>func</span> <span>=</span> <span>func</span>
<span>self</span><span>.</span><span>call_count</span> <span>=</span> <span>0</span>
<span>def</span> <span>__call__</span><span>(</span><span>self</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>self</span><span>.</span><span>call_count</span> <span>+=</span> <span>1</span>
<span>print</span><span>(</span><span>f</span><span>"</span><span>Call </span><span>{</span><span>self</span><span>.</span><span>call_count</span><span>}</span><span> to </span><span>{</span><span>self</span><span>.</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span>"</span><span>)</span>
<span>return</span> <span>self</span><span>.</span><span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>@CountCalls</span>
<span>def</span> <span>say_hello</span><span>():</span>
<span>print</span><span>(</span><span>"</span><span>Hello!</span><span>"</span><span>)</span>
<span>say_hello</span><span>()</span>
<span>say_hello</span><span>()</span>
<span># Class-based decorator </span><span>class</span> <span>CountCalls</span><span>:</span>
    <span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>func</span><span>):</span>
        <span>self</span><span>.</span><span>func</span> <span>=</span> <span>func</span>
        <span>self</span><span>.</span><span>call_count</span> <span>=</span> <span>0</span>

    <span>def</span> <span>__call__</span><span>(</span><span>self</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>self</span><span>.</span><span>call_count</span> <span>+=</span> <span>1</span>
        <span>print</span><span>(</span><span>f</span><span>"</span><span>Call </span><span>{</span><span>self</span><span>.</span><span>call_count</span><span>}</span><span> to </span><span>{</span><span>self</span><span>.</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span>"</span><span>)</span>
        <span>return</span> <span>self</span><span>.</span><span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>

<span>@CountCalls</span>
<span>def</span> <span>say_hello</span><span>():</span>
    <span>print</span><span>(</span><span>"</span><span>Hello!</span><span>"</span><span>)</span>

<span>say_hello</span><span>()</span>
<span>say_hello</span><span>()</span>
# Class-based decorator class CountCalls: def __init__(self, func): self.func = func self.call_count = 0 def __call__(self, *args, **kwargs): self.call_count += 1 print(f"Call {self.call_count} to {self.func.__name__}") return self.func(*args, **kwargs) @CountCalls def say_hello(): print("Hello!") say_hello() say_hello()

Enter fullscreen mode Exit fullscreen mode

Output:

Call 1 to say_hello
Hello!
Call 2 to say_hello
Hello!
Call 1 to say_hello
Hello!
Call 2 to say_hello
Hello!
Call 1 to say_hello Hello! Call 2 to say_hello Hello!

Enter fullscreen mode Exit fullscreen mode

Here, the call method enables the class to behave like a function, allowing it to wrap the target function seamlessly.

Decorators for Methods

Decorators work just as well with methods in classes. However, handling self correctly is essential.

<span># Method decorator example </span><span>def</span> <span>log_method</span><span>(</span><span>func</span><span>):</span>
<span>def</span> <span>wrapper</span><span>(</span><span>self</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>print</span><span>(</span><span>f</span><span>"</span><span>Method </span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span> called on </span><span>{</span><span>self</span><span>}</span><span>"</span><span>)</span>
<span>return</span> <span>func</span><span>(</span><span>self</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>return</span> <span>wrapper</span>
<span>class</span> <span>Greeter</span><span>:</span>
<span>@log_method</span>
<span>def</span> <span>greet</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>):</span>
<span>print</span><span>(</span><span>f</span><span>"</span><span>Hello, </span><span>{</span><span>name</span><span>}</span><span>!</span><span>"</span><span>)</span>
<span>obj</span> <span>=</span> <span>Greeter</span><span>()</span>
<span>obj</span><span>.</span><span>greet</span><span>(</span><span>"</span><span>Alice</span><span>"</span><span>)</span>
<span># Method decorator example </span><span>def</span> <span>log_method</span><span>(</span><span>func</span><span>):</span>
    <span>def</span> <span>wrapper</span><span>(</span><span>self</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>print</span><span>(</span><span>f</span><span>"</span><span>Method </span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span> called on </span><span>{</span><span>self</span><span>}</span><span>"</span><span>)</span>
        <span>return</span> <span>func</span><span>(</span><span>self</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
    <span>return</span> <span>wrapper</span>

<span>class</span> <span>Greeter</span><span>:</span>
    <span>@log_method</span>
    <span>def</span> <span>greet</span><span>(</span><span>self</span><span>,</span> <span>name</span><span>):</span>
        <span>print</span><span>(</span><span>f</span><span>"</span><span>Hello, </span><span>{</span><span>name</span><span>}</span><span>!</span><span>"</span><span>)</span>

<span>obj</span> <span>=</span> <span>Greeter</span><span>()</span>
<span>obj</span><span>.</span><span>greet</span><span>(</span><span>"</span><span>Alice</span><span>"</span><span>)</span>
# Method decorator example def log_method(func): def wrapper(self, *args, **kwargs): print(f"Method {func.__name__} called on {self}") return func(self, *args, **kwargs) return wrapper class Greeter: @log_method def greet(self, name): print(f"Hello, {name}!") obj = Greeter() obj.greet("Alice")

Enter fullscreen mode Exit fullscreen mode

Output:

Method greet called on <__main__.Greeter object at 0x...>
Hello, Alice!
Method greet called on <__main__.Greeter object at 0x...>
Hello, Alice!
Method greet called on <__main__.Greeter object at 0x...> Hello, Alice!

Enter fullscreen mode Exit fullscreen mode

Combining Decorators with Context Managers

Sometimes, you’ll need to integrate decorators with resource management. For instance, let’s create a decorator that times the execution of a function.

<span>import</span> <span>time</span>
<span># Timing decorator </span><span>def</span> <span>time_it</span><span>(</span><span>func</span><span>):</span>
<span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>start</span> <span>=</span> <span>time</span><span>.</span><span>time</span><span>()</span>
<span>result</span> <span>=</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>end</span> <span>=</span> <span>time</span><span>.</span><span>time</span><span>()</span>
<span>print</span><span>(</span><span>f</span><span>"</span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span> took </span><span>{</span><span>end</span> <span>-</span> <span>start</span><span>:</span><span>.</span><span>2</span><span>f</span><span>}</span><span> seconds</span><span>"</span><span>)</span>
<span>return</span> <span>result</span>
<span>return</span> <span>wrapper</span>
<span>@time_it</span>
<span>def</span> <span>slow_function</span><span>():</span>
<span>time</span><span>.</span><span>sleep</span><span>(</span><span>2</span><span>)</span>
<span>print</span><span>(</span><span>"</span><span>Done sleeping!</span><span>"</span><span>)</span>
<span>slow_function</span><span>()</span>
<span>import</span> <span>time</span>

<span># Timing decorator </span><span>def</span> <span>time_it</span><span>(</span><span>func</span><span>):</span>
    <span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>start</span> <span>=</span> <span>time</span><span>.</span><span>time</span><span>()</span>
        <span>result</span> <span>=</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
        <span>end</span> <span>=</span> <span>time</span><span>.</span><span>time</span><span>()</span>
        <span>print</span><span>(</span><span>f</span><span>"</span><span>{</span><span>func</span><span>.</span><span>__name__</span><span>}</span><span> took </span><span>{</span><span>end</span> <span>-</span> <span>start</span><span>:</span><span>.</span><span>2</span><span>f</span><span>}</span><span> seconds</span><span>"</span><span>)</span>
        <span>return</span> <span>result</span>
    <span>return</span> <span>wrapper</span>

<span>@time_it</span>
<span>def</span> <span>slow_function</span><span>():</span>
    <span>time</span><span>.</span><span>sleep</span><span>(</span><span>2</span><span>)</span>
    <span>print</span><span>(</span><span>"</span><span>Done sleeping!</span><span>"</span><span>)</span>

<span>slow_function</span><span>()</span>
import time # Timing decorator def time_it(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} took {end - start:.2f} seconds") return result return wrapper @time_it def slow_function(): time.sleep(2) print("Done sleeping!") slow_function()

Enter fullscreen mode Exit fullscreen mode

Output:

Done sleeping!
slow_function took 2.00 seconds
Done sleeping!
slow_function took 2.00 seconds
Done sleeping! slow_function took 2.00 seconds

Enter fullscreen mode Exit fullscreen mode

Best Practices

When working with decorators, keeping readability and maintainability in mind is crucial. Here are some tips:

  • Use functools.wraps: This preserves metadata of the original function.
<span>from</span> <span>functools</span> <span>import</span> <span>wraps</span>
<span>def</span> <span>simple_decorator</span><span>(</span><span>func</span><span>):</span>
<span>@wraps</span><span>(</span><span>func</span><span>)</span>
<span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
<span>return</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
<span>return</span> <span>wrapper</span>
<span>from</span> <span>functools</span> <span>import</span> <span>wraps</span>

<span>def</span> <span>simple_decorator</span><span>(</span><span>func</span><span>):</span>
    <span>@wraps</span><span>(</span><span>func</span><span>)</span>
    <span>def</span> <span>wrapper</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span>
        <span>return</span> <span>func</span><span>(</span><span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>)</span>
    <span>return</span> <span>wrapper</span>
from functools import wraps def simple_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper

Enter fullscreen mode Exit fullscreen mode

  • Test Thoroughly: Decorators can introduce subtle bugs, especially when chaining multiple decorators.

  • Document Decorators: Clearly document what each decorator does and its expected parameters.

  • Avoid Overuse: While decorators are powerful, overusing them can make code difficult to follow.

Wrapping Up

Decorators are one of Python’s most expressive features. They allow you to extend and modify behavior in a clean, reusable manner. From parameterized decorators to class-based implementations, the possibilities are endless. As you hone your skills, you’ll find yourself leveraging decorators to write cleaner, more Pythonic code—and perhaps, like a great chef, creating your signature touches in every recipe you craft.

note: AI assisted content

原文链接:Advanced Python Decorators: Elevating Your Code

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
Smash the waves would rather get in the way of the reef hill, also not willing to take a step back.
海浪宁可在挡路的礁山上撞得粉碎,也不肯后退一步
评论 抢沙发

请登录后发表评论

    暂无评论内容