Aprende leyendo en orden

Funciones de orden superior — Tratar funciones como argumentos y valores de retorno

Aprende las funciones de orden superior de Python: trata las funciones como argumentos y valores de retorno.

En el artículo anterior sobre yield viste funciones que producen valores uno a uno. Esta vez seguirás del lado de las funciones y mirarás las funciones de orden superior — el mecanismo que te permite tratar una función misma como un valor.

Tres patrones para tener presentes: asignarla a una variable, pasarla como argumento (un callback) y retornarla como valor.

Qué es una función de orden superior — Las funciones también son valores

En Python, las funciones son valores igual que los enteros o las cadenas. Puedes meterlas en variables, pasarlas a otras funciones como argumentos y retornarlas como resultado.

Una función que toma una función como argumento, o que retorna una función como resultado, se llama función de orden superior. A diferencia de print() o len(), que solo operan sobre valores, las funciones de orden superior ensamblan piezas de comportamiento.

Tres patrones que cubren las funciones de orden superior
①Asignar unafunción a una varf = printf("HOLA")②Pasar unafunción como argafter(2, greet)→ callback③Retornar unafunción como valormake_greeter("Ana")→ retorna una función
Como las funciones son valores, puedes usarlas de tres formas: asignación a variable, paso como argumento y valor de retorno.

Asignar una función a una variable — Las funciones también son valores referenciados

Cuando defines una función con def, el cuerpo de la función se construye en memoria y el nombre de la función apunta a esa ubicación. Asigna con a = print y a ahora apunta al mismo cuerpo de función, así que a("HOLA") y print("HOLA") hacen exactamente lo mismo.

El truco es dejar fuera los () después del nombre de la función. Añade () y en su lugar llamas a la función, y el valor de retorno queda asignado.

El nombre de una función es solo un nombre que apunta a un objeto función
print(nombre integrado)objeto función(cuerpo)SalidaHOLAa(tu alias)refieremisma refprint("HOLA")a("HOLA")
a = print              # sin () — asigna el cuerpo de la función a a
a("HOLA")             # HOLA  ← lo mismo que print("HOLA")

print(id(print))       # p. ej. 4395020128
print(id(a))           # aparece la misma dirección

# Tus propias funciones funcionan igual
def greet():
    print("Hola")

say = greet            # construye un alias say
say()                  # Hola

Con o sin () cambia el significado

a = print es la forma que pasa la función, mientras que a = print("HOLA") es la forma que llama a la función y pasa el resultado. En el segundo caso, el valor de retorno de print("HOLA") (None) cae en a, así que llamar a a() lanza TypeError: 'NoneType' object is not callable.

Hazte una idea de las funciones como valores, con tu propia función y con una integrada.

① Define def greet(): con un cuerpo de print("Hola").

② Construye un alias con say = greet y llama a say() — confirma que obtienes el mismo resultado que con greet().

③ Crea también un alias para la integrada len con f = len, e imprime el resultado de f("Python") con print(...).

(Cuando la respuesta sea correcta, aparecerá la explicación.)

Editor Python

Ejecutar el código para ver el resultado

Pasar una función como argumento — Callbacks

El uso más típico de las funciones de orden superior es el callback — «una función que se pasa a otra función y se llama en un momento concreto», donde «el que llama decide el comportamiento real».

Por ejemplo, supón que tienes una rutina que «imprime un saludo para una lista de tres nombres», pero quieres intercambiar solo el estilo del saludo desde el punto de llamada. Mantén el cuerpo como un bucle que extrae los nombres uno a uno, y toma la pieza de formateo como argumento — entonces el que llama puede reutilizar la rutina con solo cambiar la plantilla del saludo.

def greet_all(names, formatter):
    for name in names:
        print(formatter(name))

def formal(name):
    return f"Estimado/a {name}, gracias por tu fidelidad."

def casual(name):
    return f"¡Hola, {name}!"

greet_all(["Ana", "Carlos", "Marta"], formal)
# Estimado/a Ana, gracias por tu fidelidad.
# Estimado/a Carlos, gracias por tu fidelidad.
# Estimado/a Marta, gracias por tu fidelidad.

greet_all(["Ana", "Carlos", "Marta"], casual)
# ¡Hola, Ana!
# ¡Hola, Carlos!
# ¡Hola, Marta!
Un callback significa «pasar el comportamiento interno»
Lado del que llamagreet_all(names, formal)Función de orden superiorgreet_all(names, formatter)Para cada name,llama a formatter(name)Callbackformal(name)"Estimado/a Ana, ..."3 líneas de salidapasa fncuerpointercambiableresultado
El cuerpo de greet_all es solo un bucle; cómo convertir cada nombre en una línea de saludo se deja al argumento formatter.

Construye una función de orden superior que permita al que llama intercambiar el formato de notificación para una lista de nombres de usuario.

① Define def notify_all(users, formatter):. Con for user in users:, llama a print(formatter(user)) para cada usuario.

② Construye def login_alert(name): que retorne f"[Inicio de sesión] {name} ha iniciado sesión".

③ Construye def logout_alert(name): que retorne f"[Cierre de sesión] {name} ha cerrado sesión".

④ Prepara users = ["Ana", "Carlos"], luego llama a notify_all(users, login_alert) y notify_all(users, logout_alert).

Editor Python

Ejecutar el código para ver el resultado

Ramificar con callbacks específicos por propósito

No estás limitado a pasar un único callback. «Usa esta función en caso de éxito, esa otra en caso de fallo» es otro encaje natural — en cualquier sitio donde quieras elegir entre varios callbacks.

Por ejemplo, cuando quieres alternar el comportamiento según un número sea par o impar, la función que decide puede no hacer más que una ramificación con if, y dejar el procesamiento real a dos funciones proporcionadas por el que llama.

def process_number(number, even_callback, odd_callback):
    if number % 2 == 0:
        even_callback(number)
    else:
        odd_callback(number)

def handle_even(n):
    print(f"{n} es par")

def handle_odd(n):
    print(f"{n} es impar")

process_number(4, handle_even, handle_odd)   # 4 es par
process_number(7, handle_even, handle_odd)   # 7 es impar
Cambiar el callback según una condición
process_number(7,handle_even,handle_odd)n % 2 == 0?even_callback(n)→ handle_evenodd_callback(n)→ handle_odddecideTrueFalse

Construye una función de orden superior que enrute el procesamiento de pagos a un callback en caso de éxito y a otro en caso de fallo.

① Define def process_payment(amount, on_success, on_failure):. Si amount > 0, llama a on_success(amount); en caso contrario, llama a on_failure(amount).

② Construye def notify_success(amount): que imprima f"Pago de {amount} yenes completado".

③ Construye def notify_failure(amount): que imprima f"El importe {amount} es inválido".

④ Llama a process_payment(1500, notify_success, notify_failure) y a process_payment(0, notify_success, notify_failure).

Editor Python

Ejecutar el código para ver el resultado

Retornar una función como valor — Clausuras en la práctica

El otro patrón de orden superior es «retornar una función como valor». Retornar una función interna con return se cubrió en el artículo sobre clausuras; aquí, el foco está en el ángulo práctico de «entregar al que llama una función que ya recuerda su configuración».

Por ejemplo, cuando quieres producir en serie funciones de log que estampan el mismo prefijo en cada llamada, prepara make_logger("INFO") para el logger INFO y make_logger("ERROR") para el logger ERROR — y el código que las llama queda tan corto como info("Proceso iniciado").

def make_logger(prefix):
    def log(message):
        print(f"[{prefix}] {message}")
    return log               # retorna la función misma

info = make_logger("INFO")
error = make_logger("ERROR")

info("Proceso iniciado")     # [INFO] Proceso iniciado
error("Conexión fallida")    # [ERROR] Conexión fallida
info("Proceso finalizado")   # [INFO] Proceso finalizado
make_logger retorna una «función preconfigurada»
Módulo (espacio de nombres global)
  • info = make_logger("INFO") — recibe una función que recuerda INFO
  • error = make_logger("ERROR") — recibe una función separada que recuerda ERROR
Marco de make_logger("INFO")
  • Mantiene prefix = "INFO"
  • returna el log definido dentro
log (sigue recordando INFO)
  • Imprime [INFO] ... en cada llamada
Marco de make_logger("ERROR")
  • Mantiene prefix = "ERROR"
  • returna una función log separada
log (sigue recordando ERROR)
  • Un objeto función separado, independiente de info
Cada llamada a make_logger construye una función log separada que recuerda su prefix y la retorna. Quienes llaman la usan con nombres cortos como info() / error().

Construye una función de orden superior que retorne una función etiquetadora que recuerda su etiqueta.

① Define def make_tagger(tag):. Dentro, define def tag_text(text): que retorne f"<{tag}>{text}</{tag}>".

② Al final, retorna la función interna con return tag_text.

③ Construye dos funciones: b = make_tagger("b") e i = make_tagger("i").

④ Llama a print(b("Importante")) y print(i("Énfasis")) y confirma que aparecen <b>Importante</b> / <i>Énfasis</i> respectivamente.

Editor Python

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Qué imprime este código?
def greet():
print("Hi")
f = greet
f()

Pregunta 2¿Cuál de estas líneas pasa say_hi como callback?

Pregunta 3¿Qué imprime info("OK") después de ejecutar este código?
def make_logger(prefix):
def log(message):
print(f"[{prefix}] {message}")
return log
info = make_logger("INFO")