def measure_time(func): # func receives slow_function
def wrapper(*args, **kwargs): # This is a new function that "wraps around" the original
start_time = time.time()
result = func(*args, **kwargs) # Calls the original slow_function
end_time = time.time()
print(f"Function {func.__name__} took {end_time - start_time} seconds to run")
return result
return wrapper
@measure_time
def slow_function():
time.sleep(1)
print("Function completed")
# When you call slow_function(), this is what happens:
slow_function() # This actually calls wrapper()
start_time = time.time() # Records start time
result = func(*args, **kwargs) # Calls original slow_function which:
# - Waits 1 second (time.sleep(1))
# - Prints "Function completed"
end_time = time.time() # Records end time
# Prints something like:
# "Function slow_function took 1.001 seconds to run"
return result # Returns whatever the original function returned
More examples:
# Example 1: Logging decorator for multiple functions
def add_logging(func):
def wrapper(*args, **kwargs):
print(f"Calling function: {func.__name__}")
print(f"Arguments: {args}, {kwargs}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} finished with result: {result}")
return result
return wrapper
@add_logging
def add(a, b):
return a + b
@add_logging
def multiply(a, b):
return a * b
# Now both functions automatically get logging without repeating code
add(2, 3) # Logs: Calling function: add
# Arguments: (2, 3), {}
# Function add finished with result: 5
multiply(4, 5) # Logs: Calling function: multiply
# Arguments: (4, 5), {}
# Function multiply finished with result: 20
# Example 2: Error handling decorator
def handle_exceptions(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Error in {func.__name__}: {str(e)}")
return None
return wrapper
@handle_exceptions
def divide(a, b):
return a / b
@handle_exceptions
def get_item(list_data, index):
return list_data[index]
# Both functions now have error handling without repeating try-except
divide(10, 0) # Prints: Error in divide: division by zero
get_item([1,2,3], 5) # Prints: Error in get_item: list index out of range
# Example 3: Authentication decorator
def require_auth(func):
def wrapper(*args, **kwargs):
if not check_user_logged_in(): # Assume this function exists
return "Please log in first"
return func(*args, **kwargs)
return wrapper
@require_auth
def view_profile():
return "Here's your profile"
@require_auth
def edit_settings():
return "Edit your settings"
# Both functions now check for authentication without repeating code
# 1. Basic Decorator Structure
def my_decorator(func):
def wrapper(*args, **kwargs):
# Code before function
result = func(*args, **kwargs)
# Code after function
return result
return wrapper
# 2. Ways to Use Decorators
# Method 1: Using @ syntax
@my_decorator
def my_function():
pass
# Method 2: Direct assignment (same as above)
def my_function():
pass
my_function = my_decorator(my_function)
# 3. Common Use Cases with Examples
# Timing Decorator
def measure_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Time taken: {end_time - start_time} seconds")
return result
return wrapper
# Logging Decorator
def add_logging(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print(f"Finished {func.__name__}")
return result
return wrapper
# Argument Handling Decorator
def validate_args(func):
def wrapper(*args, **kwargs):
# Validate arguments
for arg in args:
if arg is None:
raise ValueError("None not allowed")
return func(*args, **kwargs)
return wrapper
def cache_result(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@cache_result
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
@cache_result
def expensive_api_call(url):
# Expensive API call code here
pass
def convert_to_uppercase(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if isinstance(result, str):
return result.upper()
return result
return wrapper
@convert_to_uppercase
def get_name():
return "john"
@convert_to_uppercase
def get_title():
return "developer"
The main benefits are: