Python Decorators: Ten Practical Custom Decorators with Code Examples
This article explains Python decorators, a powerful feature for modifying functions or classes, and provides ten practical custom decorators—including @timer, @memoize, @validate_input, @log_results, @suppress_errors, @validate_output, @retry, @visualize_results, @debug, and @deprecated—each with clear explanations and complete code samples.
Decorators in Python are powerful functions that modify or enhance other functions or classes without altering their original source code, enabling reusable and clean extensions.
@timer: Measure execution time
The @timer decorator records the start and end times of a function, prints the elapsed time, and returns the original result.
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.2f} seconds to execute.")
return result
return wrapper
@timer
def my_data_processing_function():
# Your data processing code here
pass@memoize: Cache results
The @memoize decorator stores function results in a dictionary keyed by arguments, avoiding repeated expensive calculations.
def memoize(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)@validate_input: Data validation
This decorator checks input data before the wrapped function runs, raising an error if validation fails.
def validate_input(func):
def wrapper(*args, **kwargs):
# Your data validation logic here
if valid_data:
return func(*args, **kwargs)
else:
raise ValueError("Invalid data. Please check your inputs.")
return wrapper
@validate_input
def analyze_data(data):
# Your data analysis code here
pass@log_results: Log output
The @log_results decorator writes the function name and its result to a log file for later inspection.
def log_results(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
with open("results.log", "a") as log_file:
log_file.write(f"{func.__name__} - Result: {result}\n")
return result
return wrapper
@log_results
def calculate_metrics(data):
# Your metric calculation code here
pass@suppress_errors: Graceful error handling
This decorator catches exceptions, prints a friendly message, and returns None so the program can continue.
def suppress_errors(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Error in {func.__name__}: {e}")
return None
return wrapper
@suppress_errors
def preprocess_data(data):
# Your data preprocessing code here
pass@validate_output: Output validation
After a function runs, this decorator checks the result against custom criteria and raises an error if the output is invalid.
def validate_output(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if valid_output(result):
return result
else:
raise ValueError("Invalid output. Please check your function logic.")
return wrapper
@validate_output
def clean_data(data):
# Your data cleaning code here
pass@retry: Automatic retries
The @retry decorator attempts to call a function multiple times with a delay between attempts, useful for flaky operations like network requests.
import time
def retry(max_attempts, delay):
def decorator(func):
def wrapper(*args, **kwargs):
attempts = 0
while attempts < max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Attempt {attempts+1} failed. Retrying in {delay} seconds.")
attempts += 1
time.sleep(delay)
raise Exception("Max retry attempts exceeded.")
return wrapper
return decorator
@retry(max_attempts=3, delay=2)
def fetch_data_from_api(api_url):
# Your API data fetching code here
pass@visualize_results: Automatic visualization
This decorator runs the original function, then creates a Matplotlib figure for custom visual output.
import matplotlib.pyplot as plt
def visualize_results(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
plt.figure()
# Your visualization code here
plt.show()
return result
return wrapper
@visualize_results
def analyze_and_visualize(data):
# Your combined analysis and visualization code here
pass@debug: Debugging helper
The @debug decorator prints the function name together with its positional and keyword arguments before execution.
def debug(func):
def wrapper(*args, **kwargs):
print(f"Debugging {func.__name__} - args: {args}, kwargs: {kwargs}")
return func(*args, **kwargs)
return wrapper
@debug
def complex_data_processing(data, threshold=0.5):
# Your complex data processing code here
pass@deprecated: Mark obsolete functions
When a function is outdated, @deprecated issues a DeprecationWarning each time the function is called.
import warnings
def deprecated(func):
def wrapper(*args, **kwargs):
warnings.warn(f"{func.__name__} is deprecated and will be removed in future versions.", DeprecationWarning)
return func(*args, **kwargs)
return wrapper
@deprecated
def old_data_processing(data):
# Your old data processing code here
passOverall, Python decorators provide a flexible way to add functionality such as timing, caching, validation, logging, error handling, retries, visualization, debugging, and deprecation warnings, making code more modular and maintainable.
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.