Master Python Decorators: Simple Steps to Enhance Functions
This tutorial explains the principle and practical usage of Python decorators, guiding you through basic examples, logging decorators, argument‑accepting decorators, and class‑based decorators, while illustrating how they wrap functions to add pre‑ and post‑execution behavior.
This article introduces the principle and usage of Python decorators in an easy‑to‑understand way.
We start with a simple function:
<code>def foo():
print("do something.")
</code>To add functionality before and after the call (e.g., logging), we can wrap the function with another function. In Python this is straightforward using a nested function:
<code>def outer(func):
def inner():
print("before execution.")
func()
print("after execution.")
return inner
</code>Usage:
<code>if __name__ == "__main__":
proxy = outer(foo)
proxy()
</code>Output:
<code>before execution.
do something.
after execution.
</code>Because everything in Python is an object, the function foo is passed as an argument to outer , which returns inner . The variable proxy therefore references inner , and calling proxy() executes the three statements inside inner .
Python provides decorator syntax sugar using the @decorator_name notation, making the pattern even simpler:
<code>def outer(func):
def inner():
# same as above
pass
return inner
@outer
def foo():
print("do something.")
if __name__ == "__main__":
foo()
</code>The @outer decorator tells Python to pass foo to outer when foo is called, thereby enhancing its behavior.
A more practical example is a logger decorator that records the call time:
<code>from datetime import datetime
def logger(func):
def wrapper(*args, **kwargs):
print('[INFO] {}, function "{}" was called '.format(datetime.now(), func.__name__))
return func(*args, **kwargs)
return wrapper
@logger
def foo():
print("do something.")
if __name__ == "__main__":
foo()
</code>If a decorator needs its own arguments, an extra level of nesting is required:
<code>def logger(msg):
def decorator(func):
def wrapper(*args, **kwargs):
print('[INFO] {}, "{}" was called with message "{}"'.format(datetime.now(), func.__name__, msg))
return func(*args, **kwargs)
return wrapper
return decorator
@logger("Maybe bored.")
def foo(name):
print("do something, " + name)
foo('Johnny')
</code>Decorators can also be implemented as classes by defining __init__ and __call__ methods:
<code>from datetime import datetime
class logger(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('[INFO] {}, function "{}" was called '.format(datetime.now(), self.func.__name__))
return self.func(*args, **kwargs)
@logger
def foo():
print("do something.")
if __name__ == "__main__":
foo()
</code>These examples illustrate how decorators can be used to inject cross‑cutting concerns such as logging, timing, or access control without modifying the original function code.
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.