Python Internals: Decorators

Published: (February 20, 2026 at 08:42 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

Let’s tear down the abstraction layer and build decorators from first principles, using heap allocation, closures, and time complexity as our guides.

If you come from a static, compiled background like C++ or Java, Python decorators can feel like black magic. You slap an @login_required above a function, and suddenly it has authentication logic. You add @app.route("/"), and suddenly it’s a web endpoint.

It feels like magic because it hides complexity. But as engineers, we know that magic is just code we haven’t understood yet.

In this post, we are going to demystify Python metaprogramming. We won’t just learn how to use decorators; we’re going to understand the memory mechanics that make them possible, build a production‑ready utility belt, and use them to fundamentally alter the algorithmic complexity of a function.

Part 1: The Mechanics (No Magic Allowed)

Before we write a decorator, we need to understand the raw materials. In Python, the mechanisms that enable decorators are first‑class functions and closures.

1. Functions are just heap‑allocated objects

  • In C, a function is a block of instructions in the .text segment.
  • In Python, a function is a full‑blown object (PyObject struct) living on the heap.

Because it’s an object you can:

  • assign it to a variable,
  • pass it as an argument to another function,
  • return it from another function.

This ability to treat functions as data is the cornerstone of metaprogramming.

2. The Engine: Closures

If you come from C++, you know that local variables die when a function’s stack frame is popped. Python is different. When an inner function references a variable from an enclosing scope, Python’s compiler notices and promotes that variable from a simple stack item to a cell object on the heap.

Even after the outer function returns, the inner function retains a reference to that cell object. This “remembered environment” is called a closure.

A closure is a function that remembers the variables from its enclosing scope even after the outer function has finished executing.

Decorators rely entirely on closures to remember which function they are wrapping.

Part 2: Building the Pattern

A decorator is fundamentally simple: it is a function that takes a function as input and returns a new function as output.

Manual decoration (the raw pattern)

# The decorator factory
def my_decorator(target_func):
    # The wrapper closure retains access to 'target_func'
    def wrapper():
        print(">>> Before execution")
        target_func()               # Calling the original function
        print(">> Before execution

Executing core logic…

Note: In production, prefer Python’s built‑in functools.lru_cache over rolling your own, as it handles cache bounding to prevent memory leaks.

Summary

Decorators aren’t magic. They are an elegant application of first‑class functions and closures. By understanding how Python handles scope and object life‑cycles on the heap, you gain the power to modify behavior, inject logic, and optimise performance cleanly and imperatively.

0 views
Back to Blog

Related posts

Read more »