Decorators are a powerful and useful tool in Python that allows you to modify the behavior of a function or class method. They are often used to add functionality to existing code in a clean and readable way. In this section, we will cover the following topics:
- What are Decorators?
- Creating Simple Decorators
- Using
functools.wraps
- Decorating Functions with Arguments
- Class Decorators
- Practical Examples
- Exercises
- What are Decorators?
A decorator is a function that takes another function and extends its behavior without explicitly modifying it. Decorators are commonly used for logging, enforcing access control and permissions, instrumentation, caching, and more.
Basic Syntax
def decorator_function(original_function): def wrapper_function(): # Code to execute before the original function print("Wrapper executed this before {}".format(original_function.__name__)) return original_function() return wrapper_function
- Creating Simple Decorators
Let's create a simple decorator to understand how it works.
Example
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 @my_decorator def say_hello(): print("Hello!") say_hello()
Explanation
my_decorator
is a function that takes another functionfunc
as an argument.wrapper
is an inner function that adds some behavior before and after callingfunc
.- The
@my_decorator
syntax is a shorthand forsay_hello = my_decorator(say_hello)
.
Output
Something is happening before the function is called. Hello! Something is happening after the function is called.
- Using
functools.wraps
functools.wraps
When you use decorators, the original function's metadata (like its name, docstring, etc.) is lost. To preserve this metadata, you can use functools.wraps
.
Example
from functools import wraps def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print("Something is happening before the function is called.") result = func(*args, **kwargs) print("Something is happening after the function is called.") return result return wrapper @my_decorator def say_hello(): """A simple function to say hello""" print("Hello!") print(say_hello.__name__) # Output: say_hello print(say_hello.__doc__) # Output: A simple function to say hello
- Decorating Functions with Arguments
Decorators can also be used with functions that take arguments.
Example
def my_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print("Something is happening before the function is called.") result = func(*args, **kwargs) print("Something is happening after the function is called.") return result return wrapper @my_decorator def greet(name): print(f"Hello, {name}!") greet("Alice")
Output
Something is happening before the function is called. Hello, Alice! Something is happening after the function is called.
- Class Decorators
Decorators can also be applied to classes. A class decorator is a function that takes a class as an argument and returns a new class or modifies the existing class.
Example
def class_decorator(cls): class WrappedClass(cls): def new_method(self): return "New method added by decorator" return WrappedClass @class_decorator class MyClass: def original_method(self): return "Original method" obj = MyClass() print(obj.original_method()) # Output: Original method print(obj.new_method()) # Output: New method added by decorator
- Practical Examples
Logging Decorator
def log_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Function {func.__name__} called with arguments {args} and {kwargs}") result = func(*args, **kwargs) print(f"Function {func.__name__} returned {result}") return result return wrapper @log_decorator def add(a, b): return a + b add(3, 5)
Timing Decorator
import time def timer_decorator(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"Function {func.__name__} took {end_time - start_time} seconds to execute") return result return wrapper @timer_decorator def slow_function(): time.sleep(2) return "Finished" slow_function()
- Exercises
Exercise 1: Create a Simple Decorator
Create a decorator named uppercase_decorator
that converts the result of a function to uppercase.
def uppercase_decorator(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result.upper() return wrapper @uppercase_decorator def greet(name): return f"Hello, {name}" print(greet("Alice")) # Output: HELLO, ALICE
Exercise 2: Create a Timing Decorator
Create a decorator named execution_time_decorator
that prints the execution time of a function.
import time def execution_time_decorator(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"Execution time: {end_time - start_time} seconds") return result return wrapper @execution_time_decorator def slow_function(): time.sleep(1) return "Done" print(slow_function()) # Output: Execution time: 1.0xxxx seconds
Exercise 3: Create a Logging Decorator
Create a decorator named logging_decorator
that logs the arguments and return value of a function.
def logging_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Calling {func.__name__} with arguments {args} and {kwargs}") result = func(*args, **kwargs) print(f"{func.__name__} returned {result}") return result return wrapper @logging_decorator def multiply(a, b): return a * b print(multiply(3, 4)) # Output: Calling multiply with arguments (3, 4) and {} # multiply returned 12
Conclusion
In this section, we have learned about decorators in Python, how to create simple decorators, use functools.wraps
to preserve function metadata, decorate functions with arguments, and apply decorators to classes. We also explored practical examples and exercises to reinforce the concepts. Decorators are a powerful tool that can help you write cleaner and more maintainable code. In the next topic, we will delve into generators and their use cases.
Python Programming Course
Module 1: Introduction to Python
- Introduction to Python
- Setting Up the Development Environment
- Python Syntax and Basic Data Types
- Variables and Constants
- Basic Input and Output
Module 2: Control Structures
Module 3: Functions and Modules
- Defining Functions
- Function Arguments
- Lambda Functions
- Modules and Packages
- Standard Library Overview
Module 4: Data Structures
Module 5: Object-Oriented Programming
Module 6: File Handling
Module 7: Error Handling and Exceptions
Module 8: Advanced Topics
- Decorators
- Generators
- Context Managers
- Concurrency: Threads and Processes
- Asyncio for Asynchronous Programming
Module 9: Testing and Debugging
- Introduction to Testing
- Unit Testing with unittest
- Test-Driven Development
- Debugging Techniques
- Using pdb for Debugging
Module 10: Web Development with Python
- Introduction to Web Development
- Flask Framework Basics
- Building REST APIs with Flask
- Introduction to Django
- Building Web Applications with Django
Module 11: Data Science with Python
- Introduction to Data Science
- NumPy for Numerical Computing
- Pandas for Data Manipulation
- Matplotlib for Data Visualization
- Introduction to Machine Learning with scikit-learn