Understanding Python Decorators: Concepts, Examples, and Benefits
This article explains what Python decorators are, how they work, and demonstrates their use with practical code examples—including timing, debugging, and memoization decorators—while highlighting benefits such as code reduction, reusability, separation of concerns, and performance improvements.
Today I will share some magical Python decorators that can reduce your code by half. Below is an explanation of how they work and why you should use them in your projects.
What is a Python decorator? A decorator is a powerful feature that lets you modify the behavior of a function or class without changing its source code. Essentially, a decorator is a function that takes another function as an argument and returns a new function that wraps the original one.
Example: a simple function that prints a message.
<code>def hello():
print("Hello, world!")
</code>Now suppose you want to measure the execution time of this function. You can write another function that uses the time module to record start and end times, then call the original function.
<code>import time
def measure_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print(f"Execution time: {end - start} seconds")
return wrapper
</code>Using the decorator:
<code>hello = measure_time(hello)
hello()
</code>Output:
<code>Hello, world!
Execution time: 0.000123456789 seconds
</code>With the syntactic sugar @ , the same result can be achieved more concisely:
<code>@measure_time
def hello():
print("Hello, world!")
hello()
</code>Why use Python decorators? They allow code reuse, avoid redundancy, separate concerns, follow the single‑responsibility principle, and let you extend existing functions or classes without modifying their source.
Examples of useful decorators
1. @timer – measures execution time for any function.
<code>import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Execution time of {func.__name__}: {end - start} seconds")
return result
return wrapper
</code>Usage:
<code>@timer
def factorial(n):
if n == 0 or n == 1:
return 1
return n * factorial(n-1)
@timer
def fibonacci(n):
if n == 0 or n == 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(factorial(10))
print(fibonacci(10))
</code>Output:
<code>Execution time of factorial: 1.19e-06 seconds
3628800
Execution time of fibonacci: 0.000123456789 seconds
55
</code>2. @debug – prints function name, arguments, and return value for debugging.
<code>from functools import wraps
def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args} and kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
</code>Usage:
<code>@debug
def add(x, y):
return x + y
@debug
def greet(name, message="Hello"):
return f"{message}, {name}!"
print(add(2, 3))
print(greet("Alice"))
print(greet("Bob", message="Hi"))
</code>Output:
<code>Calling add with args: (2, 3) and kwargs: {}
add returned: 5
5
Calling greet with args: ('Alice',) and kwargs: {}
greet returned: Hello, Alice!
Hello, Alice!
Calling greet with args: ('Bob',) and kwargs: {'message': 'Hi'}
greet returned: Hi, Bob!
Hi, Bob!
</code>3. @memoize – caches results of expensive functions to improve performance.
<code>from functools import wraps
def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
</code>Usage:
<code>@memoize
def factorial(n):
if n == 0 or n == 1:
return 1
return n * factorial(n-1)
@memoize
def fibonacci(n):
if n == 0 or n == 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(factorial(10))
print(fibonacci(10))
</code>Output is the same as before, but execution is faster because results are cached.
Conclusion
Python decorators provide a powerful and elegant way to modify the behavior of functions or classes without altering their source code. They help reduce code size, improve readability, promote reuse, separate concerns, and extend existing functionality.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.