Complete introduction and practice of Python Decorator
Have you ever encountered this scenario: after working hard to write a bunch of core business functions, you suddenly want to add an execution log to each function; or you find that several key functions need to record the **execution time ** in batches. If you directly copy and paste the repeated code at the beginning and end of the function, it will not only be cumbersome, but also require hundreds of changes during future maintenance - at this time, Python Decorator will become your savior!
Decorators are a very "Pythonic" syntax feature in Python that allow you to elegantly insert additional functionality without modifying the internal code of the original function. This article will help you start from scratch and thoroughly understand the principles and usage of decorators.
1. Decorator basics: two prerequisite knowledge
A decorator is essentially a higher-order function. To understand it, you must first understand two core concepts.
1.1 Functions are also objects
In Python, functions are “first-class citizens” just like integers, strings, and lists:
- Can be assigned to variables
- Can be used as a parameter of another function
- Can be used as the return value of another function
- Can be stored in lists, dictionaries and other containers
Let’s first look at an example of assignment:
This code illustrates that the function itself is an object that can be passed and referenced, which paves the way for decorators.
1.2 Higher-order functions
The so-called higher-order function is a function that accepts a function as a parameter or returns a function as a return value.
Below we write a simple higher-order function that receives a function and adds printing before and after its execution:
Although this works, every time you callsay_helloYou have to wrap it manuallyprepare_exec, too much trouble. can you letprepare_execDirectly "transform"say_hello, call it directly latersay_hello()Can it automatically bring forward and backward printing?
This is the prototype idea of a decorator: return a new wrapped function and replace the original function with it.
2. The first decorator: from manual to automatic
2.1 Basic decorator + @ syntax sugar
Let's put the aboveprepare_execChange to return a "wrapper function"wrapper,thiswrapperInternally, additional logic is executed before calling the original function:
Now, use the decorator syntactic sugar provided by Python@**, paste it above the function that needs to be enhanced:
Called exactly as is:
The magic behind:
@logPlacing it before the function definition is equivalent to the Python interpreter automatically executing:In other words, the original function is replaced by the new wrapped function.
2.2 Retain meta-information of the original function
There is a small problem with the above decorator: the meta-properties of the function have changed after wrapping!
This is unfriendly for debugging, documentation generation, and some tools that rely on meta-properties. The fix is simple, with the help of the Python standard libraryfunctools.wraps:
Check again:
Perfect! When writing decorators in the future, remember to develop a@functools.wrapshabits.
3. Advanced: Let the decorator accept custom parameters
In a real project, you may need the decorator to support different configurations. For example, the log decorator sometimes uses[INFO]prefix, sometimes used[DEBUG]. This requires the decorator to receive its own parameters.
The implementation method is to add a layer of functions: the outermost layer receives custom parameters, the middle layer is the original decorator, and the innermost layer is the wrapper function.
How to use:
Execution Principle:
- Execute first
log(prefix="[DEBUG]"), get the return valuedecorator- Execute again
@decorator, equivalent toadd = decorator(add)
If you think the default parameters have to be written@log()It's a bit troublesome. Section 4.2 below will give a general solution that can be used with or without parentheses.
4. Practice: Two high-frequency decorators
After finishing the theory, let’s write two decorators that are used in actual projects.
4.1 Calculate function execution time (performance analysis tool)
When doing performance optimization, you often need to know how long a certain function took to run. Note: Use heretime.perf_counter()instead oftime.time(), because the former is a short-time timer with high precision and not affected by system time adjustment.
4.2 Universal log decorator (supported by both calling methods)
If you want to make the log decorator more flexible - support both@logWithout parentheses, it is also supported@log("前缀")With parameters, you can design it like this:
Test it out:
Both writing methods are perfectly compatible!
5. Advanced: Use classes to implement decorators
In addition to functions, we can also use classes to implement decorators. Class decorators have two outstanding advantages:
- Convenient to save state (such as counting how many times a function has been called)
- Functions can be extended through inheritance
5.1 Simple class decorator
Class decorators need to be in__init__Receive the original function and retain the information in__call__Implement packaging logic in . Since there is no@functools.wrapsCan be applied directly, needs to be called manuallyfunctools.update_wrapperto preserve meta attributes.
5.2 Stateful class decorator - counting the number of calls
This is a classic use case for class decorators: using instance properties to save a count.
every callsay_hello(), the counter automatically accumulates and the status is perfectly retained.
6. Decorator best practices
- Retain original function meta-information: Always remember to use
functools.wraps(function decorator) orfunctools.update_wrapper(class decorator). - Single Responsibility: A decorator only does one thing. Don't do timing, logging, and permission checks at the same time. It's easier to reuse after splitting.
- Maintain configurability: Try to let the decorator accept optional parameters to adapt to different scenarios, just like the previous general
log。 - Avoid excessive nesting: Decorators will add a layer of abstraction. If three or four layers are stacked together, the code execution flow will become obscure and debugging will be more difficult. If it can be solved in a simple way, don't show off your skills.
7. Summary
Now looking back at the requirements of "batch adding logs and batch timing" at the beginning, do you feel that it is much easier? Write a useful decorator and add it to the header of the required function.@That's fine, the core business code is not affected at all.
The core value of decorators is:
- Non-intrusive extension: without modifying a single line of code of the original function
- Crosscutting concern processing: logging, timing, permission checking, Python’s own
@functools.lru_cacheCaching, etc., these are functions that have nothing to do with the core logic but are everywhere - Code is cleaner and more Pythonic
Mastering decorators will definitely improve the quality of your Python code. Try adding the first decorator to a function in your project now!

