Introduction
Decorators provide a simple yet powerful way to modify and extend the behavior of functions in Python. Understanding decorators allows you to dramatically simplify code by abstracting away common functionality into reusable wrappers.
In this tutorial, we will demystify decorators and understand them comprehensively through examples.
What are Python Decorators?
A decorator is a design pattern in Python that allows modifying or extending the functionality of a function, method or class without permanently changing the original. Decorators wrap the original object, intercepting calls to it and potentially modifying input or output.
Decorators build on top of closures to impart additional behavior. They are commonly used to:
- Add logging, authorization or other pre/post processing to functions
- Modify return values or input arguments to functions
- Rate limit function execution
- Manage caching and declarative memoization
- Instrument code for timing, debugging or analytics
By abstracting cross-cutting concerns into decorators, code becomes more readable, maintainable and DRY.
Defining Decorators in python
The general structure of a decorator is:
def decorator(func):
# Decorator logic
def wrapper():
# Wrapper logic
func()
return wrapper
func
is the original function passed inwrapper()
contains decorator logicwrapper
replaces and invokes the original function
Let’s see this in action:
def uppercase(func):
def wrapper():
original_result = func()
modified_result = original_result.upper()
return modified_result
return wrapper
@uppercase
def greet():
return 'hello there!'
print(greet()) # HELLO THERE!
Here uppercase
is a decorator that converts the function return value to uppercase. It is applied via the @
syntax.
Decorating Functions With Parameters
For functions with parameters, the wrapper needs to accept arguments and forward them:
def debug(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@debug
def add(x, y):
return x + y
add(5, 3)
# Calling add
# add returned 8
The wrapper transparently handles parameters intended for the original function.
Chaining Multiple Decorators
Multiple decorators can be chained sequentially to impart additional functionality:
def encrypt(func):
# Encryption decorator
def authenticate(func):
# Authentication decorator
@encrypt
@authenticate
def send_message(msg):
# Function body
Here send_message()
is decorated with authenticate()
and then encrypt()
. The decorators execute bottom-up in the order they are listed.
Class Decorators
The @classmethod
and @staticmethod
decorators are used to define class methods and static methods respectively.
For example:
class Formatter:
@staticmethod
def format(text):
# Format text
return formatted_text
@classmethod
def validate(cls, input):
# Validate input
return valid
This declares format()
as a static method and validate()
as a class method within Formatter
.
Decorators With Parameters
Sometimes decorators themselves need parameters. This can be achieved by creating a decorator factory:
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(num_times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(num_times=3)
def greet(name):
print(f"Hello {name}")
greet("John")
# Output:
# Hello John
# Hello John
# Hello John
Here repeat
is a decorator factory that accepts a parameter and returns the actual decorator
.
Conclusion
Decorators provide a clean way to modify and extend behavior while abstracting away cross-cutting concerns. Python makes working with decorators easy and intuitive. They impart simplicity and modularity to code when used judiciously. Mastering decorators enables building more scalable and maintainable Python programs.
Frequently Asked Questions
- What are decorators in Python?
Python decorators are constructs that allow modifying or extending the functionality of a function or class without permanently changing the original source code.
- How do decorators work in Python?
Python decorators wrap the original object and return a modified version. They use closure properties to store outer function state. The @ syntax is used to apply them.
- What is an example use case for decorators?
Common use cases include adding logging, rate limiting, caching, authentication, instrumentation etc. to existing code via decorators.
- Can multiple decorators be applied to a function?
Yes, decorators can be chained together sequentially using the @ syntax. They execute bottom-up inlisted order.
- When should we use a decorator versus a subclass?
Decorators are suitable for minor extensibility. For major customization, subclassing is better.
- Can decorators in python take arguments?
Yes, decorators in python can accept arguments using a factory pattern that returns the actual decorator.
- What is the difference between classmethod and staticmethod?
Classmethods receive the class as first argument while staticmethods don’t receive any special arguments.
- How do you prevent decorators from affecting the docstring?
Using functools.wraps preserves the original docstring and other metadata when decorating.
- Can decorators be class instances?
Yes, the decorator can be a callable object like a class instance. The call method is invoked by the @ syntax.
- How can decorators be debugged in Python?
The ‘functools.wraps’ decorator prints the wrapper function name correctly for easier debugging.