Entendiendo los Namespaces en Python de manera práctica

A menudo escuchamos el término namespaces en contextos como el kernel de Linux o Kubernetes, donde se utilizan principalmente para aislar o agrupar recursos de manera lógica. Pero, alguna vez te has preguntado ¿qué significa un namespace en el contexto de Python?

En este artículo quiero compartir lo que he aprendido sobre los namespaces en Python: qué son, para qué sirven y cuándo conviene utilizarlos.

¿Qué es un Namespace en Python?

Un namespace en Python es, en términos simples, un espacio donde se asignan nombres a objetos. Cada nombre (variable, función, clase, módulo, etc.) vive dentro de un namespace. Cuando se hace referencia a un identificador (por ejemplo, x), Python busca ese nombre en el namespace correspondiente para determinar a qué objeto se refiere.

Tras bambalinas, Python maneja los namespaces como diccionarios que mapean nombres a objetos.

Tipos de Namespaces

Existen distintos tipos de namespaces, y entender su jerarquía nos ayuda a comprender el alcance de los objetos en nuestro código. Los cuatro principales son:

  1. Local: Existe solo dentro de una función. Se crea al momento de invocar la función y se destruye al salir de ella.
  2. Enclosing (no local): Corresponde a las funciones anidadas, es decir, una función dentro de otra.
  3. Global: El namespace del módulo o script actual.
  4. Built-in: El nivel más alto, proporcionado por Python, donde viven funciones como len(), print(), etc.

Python utiliza una regla conocida como LEGB (Local → Enclosing → Global → Built-in) para resolver nombres en tiempo de ejecución.

globals() vs locals()

Python expone funciones integradas para inspeccionar y, en algunos casos, modificar los los namespaces en tiempo de ejecución:

  • globals() retorna una referencia al diccionario del namespace global.
  • locals() retorna una copia del namespace local (en el contexto actual).

Ejemplo:

x=10
def demo():
    y=20
    print("globals:", list(globals().keys()))
    print("locals:", locals())
demo()

Salida:

globals: ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'x', 'demo']
locals: {'y': 20}

Como se observa, globals() muestra todas las definiciones en el ámbito global, mientras que locals() muestra las variables locales disponibles dentro de la función demo().

Aunque es técnicamente posible modificar variables a través de estos diccionarios, no se recomienda hacerlo directamente. Es más claro y seguro utilizar las palabras reservadas global y nonlocal cuando se necesita alterar variables fuera del ámbito actual.

Usando global

La palabra reservada global permite modificar una variable definida en el namespace global desde dentro de una función.

Ejemplo:

counter = 0
def increase():
    global counter
    counter += 1

increase()
print(counter)

En este ejemplo, la variable counter vive en el espacio global, y gracias a la declaración global counter, la función increase() puede modificarla directamente. Sin esa declaración, Python interpretaría counter como una variable local, y lanzaría un UnboundLocalError.

Usando nonlocal

La palabra reservada nonlocal permite modicar variables de función desde una función anidada.

Ejemplo:

def counter_func():
    counter = 0

    def increase():
        nonlocal counter
        counter += 1
        return counter

    return increase

f = counter_func()
print("Fist call:", f())    # Fist call: 1
print("Second call:", f())  # Second call: 2

Aquí, nonlocal permite a la función interna increase() modificar la variable counter definida en su función envolvente counter_func(). Sin nonlocal, se crearía una nueva variable local dentro de increase().

Conclusión

Los namespaces en Python son una herramienta fundamental para organizar y controlar el alcance de las variables. Entender su funcionamiento te permitirá escribir código más claro, predecible y libre de errores sutiles relacionados con el scope. Tanto global como nonlocal deben usarse con precaución, y las funciones globals() y locals() pueden llegar a ser útiles durante la depuración de un programa.