DMarlon Studio

Lección 4 · 4 de 20

Clausuras y nonlocal · Python

Funciones que devuelven funciones y recuerdan sus variables: clausuras en Python y nonlocal para cambiarlas, con descuentos y turnos.

Python · Clausuras y nonlocal en DMarlon Studio: el código del ejemplo y su salida en la terminal

En Python una función puede definir otra dentro y devolverla. make_discount(0.20) crea la función apply y la devuelve sin llamarla. Lo curioso es que apply sigue viendo rate aunque make_discount ya haya terminado: eso es una clausura, una función que lleva consigo las variables del sitio donde nació.

Cada llamada crea una clausura distinta: sales recuerda 0.20 y members recuerda 0.35. Son como pequeñas fábricas de funciones configuradas.

Leer una variable de fuera no necesita nada especial, pero cambiarla sí. En next_ticket, number += 1 haría que Python tratara number como local (y fallaría con UnboundLocalError). La declaración `nonlocal number` indica que es la variable de la función de fuera, la que se conserva entre llamadas.

Por eso bakery y butcher llevan cuentas separadas: cada dispensador tiene su propio number. La última línea curiosea dentro: sales.__closure__[0].cell_contents es la celda donde la clausura guarda rate.

Las clausuras son la base de los decoradores (la próxima lección) y una alternativa ligera a una clase cuando solo hace falta guardar un dato y un método.

Código · main.py

def make_discount(rate):    """Returns a function that remembers rate."""    def apply(price):        return round(price * (1 - rate), 2)    return applydef make_ticket_dispenser(prefix):    """Each call hands out the next ticket number."""    number = 0    def next_ticket():        nonlocal number            # change the outer variable        number += 1        return f"{prefix}-{number:03d}"    return next_ticketsales = make_discount(0.20)members = make_discount(0.35)print("Sales price:", sales(50.0))print("Members price:", members(50.0))bakery = make_ticket_dispenser("B")butcher = make_ticket_dispenser("M")print(bakery(), bakery(), bakery())print(butcher(), butcher())print("Next bakery ticket:", bakery())# The remembered values live in the closure cellsprint("Captured rate:", sales.__closure__[0].cell_contents)

Salida · python3 main.py

Sales price: 40.0
Members price: 32.5
B-001 B-002 B-003
M-001 M-002
Next bakery ticket: B-004
Captured rate: 0.2

Para recordar

  • Una función interior puede usar las variables de la exterior.
  • Esas variables siguen vivas después de que la exterior termine.
  • nonlocal permite cambiar una variable de la función de fuera.
  • Cada llamada a la fábrica crea una clausura independiente.

Ejercicio

Escriba make_counter(limit) que devuelva una función que cuente 1, 2, 3… y, al pasar de limit, devuelva "full".