Skip to content

Latest commit

 

History

History
225 lines (162 loc) · 4.85 KB

README.md

File metadata and controls

225 lines (162 loc) · 4.85 KB

Chapter 13: Decorators

Table of Contents

Understanding Decorators

Decorators are functions that modify the behavior of other functions or methods. They allow you to wrap another function to extend its behavior without permanently modifying it.

Basic Decorator

A basic decorator function takes another function as an argument, defines a wrapper function, and returns the wrapper function.

def decorator(func):
    def wrapper():
        print("Start")
        func()
        print("End")
    return wrapper

@decorator
def ordinary():
    print("Ordinary Function")

ordinary()

Output:

Start
Ordinary Function
End

Explanation: The decorator function wraps the ordinary function, adding behavior before and after the original function call.

Function Wrapping and Augmentation

Decorators can be used to augment the behavior of functions, such as adding logging, timing, or access control.

Example: Timing a Function

import time

def timer(func):
    def wrapper():
        t1 = time.time()
        print("Start")
        func()
        print("End")
        t2 = time.time()
        res = round((t2 - t1) * 1000)
        print(f"{func.__name__} executed in {res} milliseconds")
    return wrapper

@timer
def task():
    time.sleep(3)

task()

Output:

Start
End
task executed in 3000 milliseconds

Explanation: The timer decorator measures the execution time of the task function.

Using functools.wraps

The functools.wraps decorator is used to preserve the original function's metadata when it is wrapped by a decorator.

Example: Preserving Metadata

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("Before")
        res = func(*args, **kwargs)
        print("After")
        return res
    return wrapper

@decorator
def add(x, y):
    """Adds two numbers."""
    return x + y

print(add(5, y=5))
print(add.__name__)
print(add.__doc__)

Output:

Before
After
10
add
Adds two numbers.

Explanation: The functools.wraps decorator ensures that the add function retains its original name and docstring.

Decorating Functions with Arguments

Decorators can also handle functions with arguments by using *args and **kwargs in the wrapper function.

Example: Decorating a Function with Arguments

def decorator(func):
    def wrapper(*args, **kwargs):
        print("Before")
        res = func(*args, **kwargs)
        print("After")
        return res
    return wrapper

@decorator
def add(x, y):
    return x + y

print(add(5, y=5))

Output:

Before
After
10

Explanation: The decorator function wraps the add function, allowing it to handle any number of positional and keyword arguments.

Practical Applications of Decorators

Decorators have many practical applications, such as logging, access control, and memoization.

Example: Logging

def log(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with {args} and {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log
def multiply(x, y):
    return x * y

print(multiply(3, 4))

Output:

Calling multiply with (3, 4) and {}
12

Explanation: The log decorator logs the function call with its arguments.

Example: Access Control

def requires_auth(func):
    def wrapper(*args, **kwargs):
        if not kwargs.get('authenticated', False):
            print("Authentication required")
            return
        return func(*args, **kwargs)
    return wrapper

@requires_auth
def get_data(*args, **kwargs):
    return "Sensitive data"

print(get_data(authenticated=True))
print(get_data(authenticated=False))

Output:

Sensitive data
Authentication required

Explanation: The requires_auth decorator checks if the user is authenticated before allowing access to the get_data function.

Summary

In this chapter, we covered decorators, including understanding decorators, function wrapping and augmentation, using functools.wraps, decorating functions with arguments, and practical applications of decorators.

Tasks

  1. Write a decorator that logs the execution time of a function.
  2. Write a decorator that checks if a user is authorized to access a function.
  3. Write a decorator that caches the results of a function to improve performance.
  4. Write a decorator that adds a retry mechanism to a function that may fail.

Next Chapter: Package Management with PIP