What is decorator in Python
Understanding Decorators in Python
Decorators are one of the powerful features in Python, but for beginners, they can seem like a bit of a mystery. If you've been learning Python, you might have come across this term and wondered what it's all about. In this post, we're going to unravel the concept of decorators in a way that's easy to grasp, even if you're new to programming.
The Basics: What is a Decorator?
Imagine you have a gift. To make it look nicer, you wrap it in decorative paper. You're not changing the gift inside, just adding something to it to make it special. In Python, a decorator is somewhat similar. It's a way to add new "wrapping" (functionality) to an existing function without modifying its structure.
In technical terms, a decorator is a function that takes another function as an argument, adds some kind of functionality, and then returns a new function with that added functionality.
First Steps: Functions Within Functions
To understand decorators, you first need to understand that in Python, functions are first-class citizens. This means they can be passed around and used as arguments just like any other object (like strings or numbers).
Here's a simple example of a function within another function:
def parent_function():
print("I'm the parent function.")
def child_function():
print("I'm the child function.")
child_function()
parent_function()
When you run this code, you'll see the output:
I'm the parent function.
I'm the child function.
The child_function
is defined within the parent_function
and only exists inside the parent function's scope. It's not accessible outside its parent function.
Taking it Further: Functions Returning Functions
Now, let's take it a step further. Functions can not only be defined inside other functions, but they can also return other functions.
def parent_function():
def child_function():
print("I'm the child function.")
return child_function
new_function = parent_function()
new_function()
In this example, parent_function
returns child_function
, and we store that returned function in new_function
. When we call new_function()
, we get the output:
I'm the child function.
Adding a Layer: Functions as Arguments
Functions can also take other functions as arguments. This is a key concept in understanding decorators.
def parent_function(child_function):
child_function()
def another_child_function():
print("Hello from another child function!")
parent_function(another_child_function)
When this code runs, it prints:
Hello from another child function!
Here, another_child_function
is passed to parent_function
as an argument and is called within parent_function
.
The Actual Decorator
Now that we've understood the basics, let's look at an actual decorator. A decorator is a function that wraps another function, enhancing it or changing its behavior.
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
def say_hello():
print("Hello!")
decorated_function = my_decorator(say_hello)
decorated_function()
This will output:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
The my_decorator
function is a decorator that takes a function as an argument and defines a wrapper
function inside it. The wrapper
function calls the original function and adds some print statements before and after its call.
The @ Symbol: Syntactic Sugar
Python provides a convenient way to apply decorators using the @
symbol, often referred to as "syntactic sugar". This allows us to decorate a function directly when we define it.
@my_decorator
def say_goodbye():
print("Goodbye!")
say_goodbye()
This code does exactly the same thing as the previous example but uses the @
symbol to apply the my_decorator
to say_goodbye
. It's cleaner and more readable.
Real-world Example: Timing a Function
A practical use of decorators is timing how long a function takes to run. Here's a decorator that does just that:
import time
def timing_decorator(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print(f"Function took {end_time - start_time} seconds to complete.")
return wrapper
@timing_decorator
def long_running_function():
for _ in range(1000000):
pass
long_running_function()
This decorator, when applied to a function, will print out the time it took for the function to execute.
Decorators with Arguments
Sometimes, you might want to pass arguments to the function being decorated. Here's how you can modify the decorator to accept arguments:
def decorator_with_arguments(func):
def wrapper(*args, **kwargs):
print("Arguments were passed to the function.")
return func(*args, **kwargs)
return wrapper
@decorator_with_arguments
def function_with_arguments(greeting, name):
print(f"{greeting}, {name}!")
function_with_arguments("Hello", "Alice")
The *args
and **kwargs
syntax allows the wrapper
function to accept any number of positional and keyword arguments, which are then passed to the function being decorated.
Intuition and Analogies
Think of decorators as custom stickers for your laptop. Your laptop functions perfectly well without them, but adding a sticker can give it a personal touch (additional functionality). Decorators work the same way; they add to your functions without changing their core purpose.
Conclusion
Decorators are a powerful tool in Python that allows you to modify the behavior of functions in a clean, readable way. They can be a bit tricky to wrap your head around at first, but once you understand the concept of functions as objects that can be passed around, returned, and taken as arguments, the rest falls into place.
Just like a skilled artist uses layers to create a beautiful painting, decorators in Python allow you to layer functionality onto your functions. They encourage code reusability and can make your code more modular and elegant. So next time you find yourself repeating the same code over multiple functions, consider whether a decorator could help you "wrap" up that functionality in a more efficient way. With practice, you'll find that decorators can be a gift that keeps on giving in your Python projects.