Course Outline (Part 24)

A decorator is a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure. Decorators are usually called before the definition of a function you want to decorate.

Before we learn decorators, we must understand how Python treats functions.


1. Functions as First-Class Objects

In Python, functions are first-class objects. This means that functions can be passed around and used as arguments, just like any other object (string, int, float, list, and so on).

def say_hello(name):
    return f"Hello {name}"

def greet_bob(greeter_func):
    return greeter_func("Bob")

print(greet_bob(say_hello)) # Hello Bob

2. Nested Functions

We can define functions inside other functions.

def parent():
    print("Printing from the parent() function")

    def first_child():
        print("Printing from the first_child() function")

    def second_child():
        print("Printing from the second_child() function")

    second_child()
    first_child()

parent()

Furthermore, a function can return another function.


3. Simple Decorator Syntax (@)

A decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.

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

def say_whee():
    print("Whee!")

say_whee = my_decorator(say_whee)
say_whee()

The @ Syntax (Syntactic Sugar)

Instead of writing say_whee = my_decorator(say_whee), Python allows you to use decorators in a simpler way with the @ symbol.

def my_decorator(func):
    def wrapper():
        print("Before execution")
        func()
        print("After execution")
    return wrapper

@my_decorator
def say_whee():
    print("Whee!")

say_whee()

4. Decorator with Arguments

What if the function we want to decorate takes arguments? We need to use *args and **kwargs in the inner wrapper function.

def do_twice(func):
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        func(*args, **kwargs)
    return wrapper_do_twice

@do_twice
def greet(name):
    print(f"Hello {name}")

greet("World")

Output:

Hello World
Hello World

5. Chaining Decorators

You can apply multiple decorators to a single function by stacking them. The decorators are applied from bottom to top.

def make_bold(func):
    def wrapper():
        return "<b>" + func() + "</b>"
    return wrapper

def make_italic(func):
    def wrapper():
        return "<i>" + func() + "</i>"
    return wrapper

@make_bold
@make_italic
def hello():
    return "Hello World"

print(hello()) # <b><i>Hello World</i></b>

Discussion

Loading comments...