Decorators in Python
Decorators in Python are a powerful and flexible way to modify the behavior of a function or class. They allow you to wrap another function to extend the wrapped function's behavior without permanently modifying it.
Understanding First-Class Functions
To understand decorators, it's essential to understand that functions are first-class objects in Python. This means that:
- Functions can be assigned to variables
- Functions can be passed as arguments to other functions
- Functions can be returned from other functions
Here's an example that demonstrates these properties:
def shout(text):
return text.upper()
print(shout('hello')) # Output: HELLO
yell = shout
print(yell('hello')) # Output: HELLO
In this example, we define a function shout()
that takes a string and returns it in uppercase. We then assign the shout()
function to a variable yell
, and call yell()
with the same argument, getting the same result.
Defining a Simple Decorator
A decorator is a function that takes another function as an argument, adds some functionality to it, and returns a new function. Here's a simple example:
def uppercase(func):
def wrapper():
result = func()
return result.upper()
return wrapper
@uppercase
def say_hello():
return "hello"
print(say_hello()) # Output: HELLO
In this example, the uppercase()
function is a decorator. It takes a function func
as an argument, and returns a new function wrapper()
that calls func()
and then converts the result to uppercase.
The @uppercase
syntax is a shorthand way of applying the uppercase()
decorator to the say_hello()
function. This is equivalent to writing:
say_hello = uppercase(say_hello)
When we call say_hello()
, the wrapper()
function is executed, which in turn calls the original say_hello()
function and returns the result in uppercase.
Decorators with Arguments
You can also create decorators that take arguments. This allows you to customize the behavior of the decorator. Here's an example:
def repeat(n):
def decorator(func):
def wrapper():
result = func()
return result * n
return wrapper
return decorator
@repeat(3)
def say_hello():
return "hello"
print(say_hello()) # Output: hellohellohello
In this example, the repeat()
function is a decorator factory that takes an argument n
and returns a decorator function. The decorator function then wraps the original function and repeats the result n
times.
The @repeat(3)
syntax applies the repeat()
decorator to the say_hello()
function, with the argument 3
.
Decorators with Arguments and Return Values
Decorators can also handle functions with arguments and return values. Here's an example:
def logged(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with {args} and {kwargs}")
return func(*args, **kwargs)
return wrapper
@logged
def add(a, b):
return a + b
print(add(2, 3)) # Output:
# Calling add with (2, 3) and {}
# 5
In this example, the logged()
decorator takes a function func
as an argument, and returns a new function wrapper()
that logs the function call before executing the original function. The *args
and **kwargs
syntax allows the wrapper()
function to handle functions with any number of positional and keyword arguments.
Decorators with Classes
Decorators can also be implemented using classes. Here's an example:
class Uppercase:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
result = self.func(*args, **kwargs)
return result.upper()
@Uppercase
def say_hello(name):
return f"hello, {name}"
print(say_hello("Alice")) # Output: HELLO, ALICE
In this example, the Uppercase
class is a decorator that takes a function func
in its __init__()
method, and defines a __call__()
method that calls the original function and converts the result to uppercase.
The @Uppercase
syntax applies the Uppercase
decorator to the say_hello()
function.
Conclusion
Decorators in Python are a powerful and flexible way to modify the behavior of functions and classes. By understanding the concept of first-class functions and how to define and apply decorators, you can write more modular, reusable, and maintainable code.
The examples provided here cover the basic concepts of decorators. Still, there are many more advanced use cases and techniques that you can explore, such as nested decorators, decorator arguments, and decorating classes.