Python Series #2 Advanced Python Decorators

Lesson 1: Review of Basic Decorators

A decorator is a function that takes another function as input and returns a new function that extends or modifies the behavior of the input function. Basic decorator usage:

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()

Lesson 2: Decorators with Arguments

To create decorators with arguments, use another level of nested functions:

def my_decorator_with_args(arg1, arg2):
    def decorator(func):
        def wrapper():
            print(f"Decorator argument 1: {arg1}")
            print(f"Decorator argument 2: {arg2}")
            func()
        return wrapper
    return decorator

@my_decorator_with_args("Hello", "World")
def say_hello():
    print("Hello!")

say_hello()

Lesson 3: Chaining Decorators

You can chain multiple decorators to apply them sequentially:

def decorator1(func):
    def wrapper():
        print("Decorator 1")
        func()
    return wrapper

def decorator2(func):
    def wrapper():
        print("Decorator 2")
        func()
    return wrapper

@decorator1
@decorator2
def say_hello():
    print("Hello!")

say_hello()

Lesson 4: Decorating Classes

You can also use decorators to modify classes and their methods:

def uppercase_decorator(cls):
    for name, method in cls.__dict__.items():
        if callable(method):
            setattr(cls, name, my_decorator(method))
    return cls

@uppercase_decorator
class MyClass:
    def say_hello(self):
        print("Hello!")

    def say_goodbye(self):
        print("Goodbye!")

my_instance = MyClass()
my_instance.say_hello()
my_instance.say_goodbye()

Lesson 5: Practical Use Case: Timing Functions

Use decorators to measure the execution time of functions:

import time

def timing_decorator(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:.2f} seconds to execute.")
        return result
    return wrapper

@timing_decorator
def slow_function():
    time.sleep(2)

slow_function()

This course should provide you with an understanding of advanced Python decorators. To learn more, you can refer to the official Python documentation or other resources such as tutorials or books.