Python decorators are a powerful feature that allows you to modify or extend the behavior of functions or methods without changing their actual code. Let’s explore how decorators work with some simple examples.
Example 1: Logging Decorator
The logging decorator adds functionality to log information about when a function is called and what it returns.
def logger(func):
def wrapper(*args, **kwargs):
print(f'Calling {func.__name__} with args={args}, kwargs={kwargs}')
result = func(*args, **kwargs)
print(f'{func.__name__} returned {result}')
return result
return wrapper
When you decorate a function with @logger
, it prints messages before and after calling the function, showing its arguments and return value.
Example 2: Timing Decorator
The timing decorator measures how much time a function takes to execute.
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f'{func.__name__} took {end_time - start_time} seconds to execute')
return result
return wrapper
With @timer
applied to a function, it calculates and prints the execution time in seconds.
Example 3: Authentication Decorator
The authentication decorator restricts access to a function based on user login status.
logged_in_users = ['alice', 'bob']
def authenticate(func):
def wrapper(username, *args, **kwargs):
if username in logged_in_users:
return func(username, *args, **kwargs)
else:
raise PermissionError(f'User {username} is not logged in')
return wrapper
When decorated with @authenticate
, the function can only be accessed by users in logged_in_users
.
Example 4: Memoization Decorator
The memoization decorator caches results of a function to optimize performance for repeated calls with the same arguments.
def memoize(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
else:
result = func(*args)
cache[args] = result
return result
return wrapper
Functions decorated with @memoize
store computed results, returning cached results for identical arguments to avoid redundant calculations.
Using Decorators
@logger
def add(a, b):
return a + b
@timer
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
@authenticate
def protected_function(username, message):
return f'{username}: {message}'
@memoize
def factorial(n):
if n == 0 or n == 1:
return 1
else:
return n * factorial(n-1)
# Testing each decorated function
print(add(3, 5)) # Output: Calling add with args=(3, 5), kwargs={}, add returned 8, 8
print(fibonacci(10)) # Output: fibonacci took 0.0 seconds to execute, 55
print(protected_function('alice', 'Hello!')) # Output: alice: Hello!
try:
print(protected_function('eve', 'Hi!')) # Raises PermissionError
except PermissionError as e:
print(e)
print(factorial(5)) # Output: 120
print(factorial(3)) # Output: 6
Each decorator adds specific functionality to the decorated functions:
-
@logger
logs function calls and returns. -
@timer
measures execution time. -
@authenticate
restricts function access based on user login. -
@memoize
caches function results to enhance performance.
Python decorators are versatile tools for adding cross-cutting concerns to functions, promoting code reuse and enhancing readability. They are widely used in frameworks and libraries to simplify and extend functionality without modifying core code.
Top comments (0)