Codeskill

Learn to code, step by step

Decorators and Generators: Advanced Python Features

Python decorators and generators matters once your pages get past the basics. Two features that wrap functions and produce values lazily.

Understanding decorators

A decorator is a function that wraps another function to change its behaviour without editing the original. It gives you a tidy way to call higher-order functions.

Basic decorator

Start with a simple example:

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_hello():
    print("Hello!")

# Decorate the function
say_hello = my_decorator(say_hello)

say_hello()

When you call say_hello(), it prints a message, runs the original function, then prints another message.

The @ syntax

Python also lets you apply decorators with the @ symbol – syntactic sugar that reads more clearly:

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

That is the same as say_hello = my_decorator(say_hello), just shorter.

Understanding generators

Generators are a simple way to build iterators. You write them like normal functions, but they use yield to return values one at a time. Each call to yield produces the next value, which makes them work in a for loop.

Creating a generator

Here is a minimal example:

def my_generator():
    yield 1
    yield 2
    yield 3

g = my_generator()

for value in g:
    print(value)

Output:

1
2
3

Why use generators?

Generators compute values on demand instead of building a full list in memory. That matters when you are working with large datasets.

Generator expressions

Like list comprehensions, but with round parentheses () instead of square brackets []:

my_gen = (x * x for x in range(3))

for x in my_gen:
    print(x)

That prints 0, 1, and 4 – the squares of 0 to 2.

Combining decorators and generators

You can use both together. For example, a decorator can time how long a generator function takes:

import time

def timing_function(some_function):
    """
    Outputs the time a function takes
    to execute.
    """
    def wrapper():
        t1 = time.time()
        some_function()
        t2 = time.time()
        return f"Time it took to run the function: {t2 - t1} \n"
    return wrapper

@timing_function
def my_generator():
    num_list = []
    for num in (x * x for x in range(10000)):  # Generator expression
        num_list.append(num)
    print("\nSum of squares: ", sum(num_list))

my_generator()

Decorators change how functions behave. Generators produce values lazily. Both turn up often once you move past beginner Python – worth trying in small scripts until they feel familiar.

PreviousUnderstanding Object-Oriented Programming in Python