En la lección 04-01 apareció un detalle que dejamos aparcado: escribir pausar sin paréntesis no da error. Python responde con algo como <function pausar at 0x7f3c9a1b2e50>, y type(pausar) devuelve <class 'function'>. Lo tratamos entonces como una trampa que evitar; ahora vamos a darle la vuelta y convertirlo en una herramienta.
Porque ese detalle significa algo importante: en Python, una función es un valor como cualquier otro. Si es un valor, se puede guardar en una variable, imprimir, pasar como argumento a otra función y devolver desde una función. Y eso abre una forma de escribir código distinta: en vez de decidir dentro de una función qué hará exactamente, se le pasa el comportamiento desde fuera.
Es la última pieza del módulo, y la que permite rematar TareaFácil: las tres funciones pedir_texto, pedir_opcion y pedir_entero comparten el mismo esqueleto —pregunta, comprueba, insiste— y solo se diferencian en qué comprobación hacen. Si la comprobación puede ser un argumento, las tres se convierten en una.
Contenido
- Una función sin paréntesis es un valor
- Funciones de orden superior
- Funciones anónimas con
lambda lambdaodef: cuándo cada uno- Devolver una función: fábricas
- El patrón callback en TareaFácil
- Orden superior sobre colecciones: un adelanto
- Errores comunes y consejos
- Ejercicios
- Conclusión del módulo
- Una función sin paréntesis es un valor
Empecemos por comprobarlo en el REPL, sin trucos.
def saludar():
print("Buenos dias, equipo de Estudio Alba.")
print(saludar) # <function saludar at 0x7f3c9a1b2e50>
print(type(saludar)) # <class 'function'>
print(saludar()) # ejecuta la funcion y luego imprime NoneLas dos primeras líneas tratan a saludar como dato: lo imprimen y consultan su tipo, igual que harías con un número. La tercera lo trata como acción: los paréntesis son el operador de llamada.
Si es un valor, se puede asignar a otra variable:
def mostrar_cabecera():
print("TareaFacil - Estudio Alba")
pintar = mostrar_cabecera # sin parentesis: copiamos la referencia
pintar() # TareaFacil - Estudio Alba
print(pintar is mostrar_cabecera) # TrueFíjate bien en lo que ha pasado: no se ha copiado la función, sino que ahora hay dos nombres apuntando al mismo objeto función, tal como viste con las referencias en 04-03. El is lo confirma. A partir de ese momento, pintar() y mostrar_cabecera() son exactamente la misma llamada.
| Expresión | Qué es | Qué vale |
|---|---|---|
mostrar_cabecera |
El objeto función | <function mostrar_cabecera at 0x…> |
mostrar_cabecera() |
Una llamada | Lo que devuelva (aquí, None) |
type(mostrar_cabecera) |
El tipo del objeto | <class 'function'> |
Esto vale también para las funciones integradas y para los métodos: str.upper, len o input sin paréntesis son valores que puedes guardar y pasar por ahí. Es la base de todo lo que viene.
- Funciones de orden superior
Una función de orden superior es aquella que recibe otra función como parámetro o devuelve una función. Suena abstracto; el ejemplo lo aclara en tres líneas.
def aplicar_formato(texto, transformacion):
"""Aplica al texto la transformacion recibida y devuelve el resultado."""
return transformacion(texto)
print(aplicar_formato("cartel feria del libro", str.upper))
print(aplicar_formato("CARTEL FERIA DEL LIBRO", str.lower))
print(aplicar_formato("cartel feria del libro", str.title))Dentro de aplicar_formato, el parámetro transformacion es un nombre local que apunta a la función que le hayan pasado; al escribir transformacion(texto) se llama a lo que sea que haya llegado. La función no sabe ni le importa cuál es: el comportamiento viene de fuera.
Y funciona igual con funciones propias:
def marcar_urgente(texto):
"""Devuelve el texto rodeado de marcas de urgencia."""
return f">>> {texto.upper()} <<<"
def con_longitud(texto):
"""Devuelve el texto seguido de su numero de caracteres."""
return f"{texto} ({len(texto)} caracteres)"
print(aplicar_formato("cartel feria del libro", marcar_urgente))
print(aplicar_formato("cartel feria del libro", con_longitud))Observa el detalle decisivo: en aplicar_formato(texto, marcar_urgente), marcar_urgente va sin paréntesis. Estamos pasando la función, no su resultado. Si escribieras marcar_urgente(...) estarías llamándola ahí mismo y pasando la cadena que devuelve, que es otra cosa completamente distinta —y la fuente número uno de errores en esta lección.
La ventaja de este patrón es que separa lo que se repite de lo que cambia. aplicar_formato es un esqueleto; la transformación concreta es un hueco que se rellena en cada llamada. Verás que es exactamente el problema de nuestras tres funciones pedir_*.
- Funciones anónimas con
lambda
lambdaA veces la función que quieres pasar es tan pequeña que darle nombre y tres líneas de def resulta desproporcionado. Para eso está lambda: una expresión que crea una función sin nombre.
Las tres partes: la palabra lambda, los parámetros separados por comas (sin paréntesis) y, tras los dos puntos, una única expresión cuyo valor se devuelve automáticamente. No se escribe return: está implícito.
Esa lambda es equivalente, punto por punto, a def doble(n): return n * 2. Su tipo es el mismo, function, y se llama igual. Donde de verdad brilla es dentro de otra llamada, sin darle nombre:
print(aplicar_formato("cartel feria", lambda t: t.strip().capitalize()))
print(aplicar_formato("cartel feria", lambda t: f"[{t}]"))
print(aplicar_formato("cartel feria", lambda t: t.replace(" ", "_")))Tres comportamientos distintos sin escribir tres def. La limitación, que es fuerte, ya la has visto: una lambda solo puede contener una expresión. Nada de if/elif en varias líneas, nada de bucles, nada de asignaciones, nada de varias sentencias. Sí cabe el operador ternario de 03-01, porque es una expresión:
etiqueta = lambda dias: "larga" if dias > 5 else "corta"
print(etiqueta(9)) # larga
print(etiqueta(2)) # cortaY una advertencia de estilo que conviene interiorizar ya: doble = lambda n: n * 2 es correcto pero desaconsejado. Si vas a ponerle nombre a la función, usa def: te da docstring, un nombre visible en los mensajes de error y sitio para crecer. La lambda está pensada para funciones sin nombre, usadas en el sitio.
lambda o def: cuándo cada uno
lambda o def: cuándo cada uno| Criterio | lambda |
def |
|---|---|---|
| Nombre | Anónima | Tiene nombre propio |
| Cuerpo | Una sola expresión | Tantas sentencias como haga falta |
return |
Implícito | Explícito |
| Docstring | No admite | Sí |
| En un traceback aparece como | <lambda> |
El nombre real de la función |
| Uso típico | Argumento de otra función, de usar y tirar | Todo lo demás |
| Reutilizable desde varios sitios | Mal | Bien |
La regla de decisión práctica cabe en tres preguntas. ¿La vas a usar más de una vez? Entonces def. ¿Necesita más de una línea, o merece una explicación? Entonces def. ¿Es una condición o una transformación trivial que se lee mejor dentro de la llamada que como una definición aparte, tres pantallas más arriba? Entonces lambda.
Ese último caso es real y no es raro: cuando el criterio es evidente, escribirlo en el mismo sitio donde se usa ahorra al lector un viaje a otra parte del fichero. Pero en cuanto la lambda empieza a tener paréntesis anidados o un ternario dentro de otro, ha dejado de aclarar y toca ascenderla a def.
- Devolver una función: fábricas
La otra mitad del orden superior es devolver una función. Una función que fabrica funciones se llama, informalmente, una fábrica, y se apoya en las funciones anidadas y el ámbito envolvente que viste en 04-03.
def crear_saludo(nombre_estudio):
"""Devuelve una funcion que saluda en nombre del estudio indicado."""
def saludar(persona):
return f"Hola {persona}, bienvenida a {nombre_estudio}."
return saludar # sin parentesis: devolvemos la funcion
saludar_alba = crear_saludo("Estudio Alba")
saludar_sole = crear_saludo("Panaderia Sole")
print(saludar_alba("Marta"))
print(saludar_alba("Luis"))
print(saludar_sole("Nuria"))Hola Marta, bienvenida a Estudio Alba. Hola Luis, bienvenida a Estudio Alba. Hola Nuria, bienvenida a Panaderia Sole.
Analiza lo que ocurre, porque es más sutil de lo que parece. crear_saludo("Estudio Alba") se ejecuta, define la función interna saludar y la devuelve; en ese momento crear_saludo termina, y según lo que aprendiste en 04-03 su variable local nombre_estudio debería desaparecer. Pero no desaparece: la función devuelta se lleva consigo el valor que tenía. Por eso saludar_alba y saludar_sole son dos funciones distintas, cada una con su estudio dentro.
Ese mecanismo tiene nombre —clausura, o closure— y no necesitas dominarlo hoy. Basta con quedarte con la idea: una fábrica te permite crear versiones especializadas de una función a partir de unos parámetros de configuración, en vez de repetir esos parámetros en todas las llamadas.
def crear_validador_de_rango(minimo, maximo):
"""Devuelve una funcion que comprueba si un texto es un entero del rango."""
def es_valido(texto):
return texto.isdigit() and minimo <= int(texto) <= maximo
return es_valido
es_dia_valido = crear_validador_de_rango(1, 365)
es_hora_valida = crear_validador_de_rango(0, 23)
print(es_dia_valido("400")) # False
print(es_dia_valido("4")) # True
print(es_hora_valida("22")) # TrueDos validadores completos a partir de una sola definición. Guarda esta idea: la vamos a usar ahora mismo.
- El patrón callback en TareaFácil
Mira las tres funciones de 04-02 quitándoles la carne:
valor = input(mensaje).strip()
while <la comprobacion que sea>:
valor = input(<el aviso que sea>).strip()
return valorLas tres tienen el mismo esqueleto y difieren solo en la comprobación. Ya sabes qué hacer: convertir esa comprobación en un parámetro. A la función que se pasa para que la llame otra se le llama callback —«función de retorno de llamada»—, porque quien la recibe la llama de vuelta cuando la necesita.
def pedir_dato(mensaje, validador, aviso="Valor no valido."):
"""Pide un dato y lo devuelve cuando el validador lo da por bueno."""
valor = input(mensaje).strip()
while not validador(valor):
print(aviso)
valor = input(mensaje).strip()
return valorOcho líneas que sustituyen a las tres funciones anteriores. El contrato es explícito: validador debe ser una función que reciba un texto y devuelva True o False. Y ahora los validadores, unos con def y otros con lambda, según lo que aconseja la tabla de la sección 4:
PRIORIDADES = ("alta", "media", "baja")
EQUIPO = ("marta", "luis", "nuria")
def es_dias_valido(texto):
"""Comprueba que el texto sea un entero de dias entre 1 y 365."""
return texto.isdigit() and 1 <= int(texto) <= 365
titulo = pedir_dato("Titulo : ",
lambda t: t != "",
"El titulo no puede estar vacio.")
responsable = pedir_dato("Responsable : ",
lambda t: t.lower() in EQUIPO,
f"Debe ser uno de: {', '.join(EQUIPO)}.")
prioridad = pedir_dato("Prioridad : ",
lambda t: t.lower() in PRIORIDADES,
"Prioridad: alta, media o baja.")
dias = int(pedir_dato("Dias (1-365) : ", es_dias_valido,
"Debe ser un entero entre 1 y 365."))Cuatro observaciones sobre este código:
- Las tres primeras validaciones son triviales y van en
lambda, escritas justo donde se usan: se lee de un vistazo qué se considera válido para cada campo. La de días lleva dos comprobaciones encadenadas y merece undefcon nombre y docstring. es_dias_validova sin paréntesis. Estamos pasando la función, no llamándola. Es el error que más se comete aquí.- El aviso es un parámetro con valor por defecto (04-02), así que cada campo puede explicar su propio criterio sin obligar a hacerlo siempre.
pedir_datodevuelve siempre texto, y la conversión a entero se hace fuera, en la llamada. Es coherente con 04-04: la función de entrada valida, y quien la usa decide qué tipo quiere.
¿Ha mejorado el programa? Depende de qué valores. La tabla honesta:
Tres funciones pedir_* (04-02) |
Una pedir_dato con callback |
|
|---|---|---|
| Funciones que mantener | 3 | 1 |
| Flexibilidad | Solo los tres casos previstos | Cualquier criterio imaginable |
| Legibilidad de la llamada | Muy alta (pedir_entero(m, 1, 365)) |
Media: hay que leer la lambda |
| Dificultad para un principiante | Baja | Media |
No hay un ganador absoluto, y conviene decirlo claro: el orden superior es una herramienta, no una mejora automática. Para un programa pequeño con tres tipos de dato, las tres funciones específicas se leen mejor. Cuando los criterios de validación se multiplican y cambian a menudo, pedir_dato gana por goleada. Lo importante es que ahora puedes elegir, y que reconoces el patrón cuando lo veas en código ajeno, donde es habitual.
- Orden superior sobre colecciones: un adelanto
El sitio donde el orden superior se usa a diario es sobre colecciones de datos: ordenar una lista de tareas por prioridad, quedarse con las de Nuria, transformarlas todas de golpe. Las herramientas clásicas son sorted con su parámetro key, y map y filter.
Como todavía no conoces las listas, veámoslo con lo que sí tienes: una cadena, que es una secuencia de caracteres. (sorted devuelve una lista de caracteres, así que los volvemos a unir con "".join(...) para poder imprimirlos como texto; las listas llegan en el módulo 5.)
print("".join(sorted("nuria"))) # ainru
print("".join(sorted("Nuria"))) # Nairu
print("".join(sorted("Nuria", key=str.lower))) # aiNru
print(max("Marta", "Luis", key=len)) # MartaFíjate en key=str.lower: es un argumento por nombre cuyo valor es una función. sorted la llama una vez por cada carácter y ordena según lo que devuelva, no según el carácter original. Por eso la tercera línea ignora mayúsculas y minúsculas mientras que la segunda pone la N al principio —las mayúsculas van antes en el orden de los caracteres. Y max(..., key=len) elige la cadena más larga en vez de la mayor alfabéticamente: el mismo max de siempre, con un criterio distinto inyectado desde fuera.
Ese es el patrón entero. sorted no sabe ordenar tareas por prioridad, pero sí sabe ordenar cualquier cosa si le dices con una función qué mirar de cada elemento. Cuando llegues a Listas y arreglos tendrás colecciones de verdad sobre las que aplicarlo, y en Algoritmos de ordenamiento verás cómo funciona la ordenación por dentro. map y filter, que aplican una función a todos los elementos o seleccionan los que la cumplen, se estudian también con colecciones; por ahora basta con que reconozcas la forma cuando la encuentres.
Errores Comunes y Consejos
Pasar la llamada en vez de la función. aplicar_formato(texto, marcar_urgente()) ejecuta marcar_urgente ahí mismo —además sin argumentos, con lo que da TypeError— y pasa su resultado. Para pasar la función, escríbela sin paréntesis.
Olvidar los paréntesis dentro de la función de orden superior. El error simétrico: si dentro escribes return transformacion en vez de transformacion(texto), devuelves el objeto función. Síntoma: se imprime <function … at 0x…> donde esperabas un texto.
Meter demasiado en una lambda. Si necesitas un if de varias ramas, un bucle o una asignación, no cabe: SyntaxError. Usa def.
Poner nombre a una lambda. doble = lambda n: n * 2 funciona, pero pierdes docstring y un nombre útil en los errores. Si lleva nombre, que sea def.
Confundir el parámetro con la función que llega. Dentro de pedir_dato, validador es un nombre local: puede recibir hoy una lambda y mañana un def. La función solo exige que sea llamable con un texto y devuelva un booleano.
Consejo: documenta el contrato del callback. En el docstring, di qué debe recibir y qué debe devolver la función que te pasan. Sin eso, quien use tu función tendrá que leerse el cuerpo.
Consejo: no uses orden superior porque quede sofisticado. Si dos if resuelven el problema con más claridad, dos if. La abstracción se paga en esfuerzo de lectura, y solo compensa cuando ahorra repetición real.
Ejercicios
Ejercicio 1: Predecir la salida
Di qué imprime cada línea, y en su caso qué error se produce y por qué.
def enmarcar(texto):
return f"<<{texto}>>"
f = enmarcar
print(f("Panaderia Sole"))
print(f)
print(enmarcar is f)
def aplicar(valor, funcion):
return funcion(valor)
print(aplicar("Panaderia Sole", enmarcar))
print(aplicar("Panaderia Sole", enmarcar("Vidal")))Ejercicio 2: Fábrica de etiquetadores
Escribe crear_etiquetador(prefijo, ancho), una función que devuelva otra función. La función devuelta recibe un texto y devuelve el prefijo alineado a la izquierda en 10 caracteres seguido del texto alineado a la derecha en ancho caracteres, usando el formateo de f-strings de 02-03. Crea con ella dos etiquetadores —uno para tareas urgentes con prefijo [URGENTE] y ancho 30, y otro para notas con prefijo [nota] y ancho 24— y pruébalos con «Cartel feria del libro».
Ejercicio 3: Validadores para TareaFácil
Usando pedir_dato(mensaje, validador, aviso) de la sección 6, escribe las expresiones de llamada necesarias para pedir estos tres datos, decidiendo en cada caso si el validador debe ser lambda o def y justificándolo:
- Un código de cliente: exactamente cuatro dígitos.
- Una respuesta de sí o no:
son, sin distinguir mayúsculas. - Un porcentaje de avance: entero entre 0 y 100, y además múltiplo de 5.
Soluciones
Solución 1.
<<Panaderia Sole>> <function enmarcar at 0x...> True <<Panaderia Sole>> TypeError: 'str' object is not callable
f = enmarcar crea un segundo nombre para el mismo objeto, así que f("Panaderia Sole") enmarca igual y enmarcar is f es True. print(f) muestra la representación del objeto función. La primera llamada a aplicar pasa la función y funciona. La última pasa enmarcar("Vidal"), que se evalúa antes de la llamada y vale "<<Vidal>>": dentro de aplicar, funcion es una cadena, y al intentar funcion(valor) Python protesta con 'str' object is not callable. Es el mismo mensaje que veías al sombrear un built-in en 04-03, y por el mismo motivo: se está llamando a algo que no es una función.
Solución 2.
def crear_etiquetador(prefijo, ancho):
"""Devuelve una funcion que etiqueta textos con el prefijo y ancho dados."""
def etiquetar(texto):
return f"{prefijo:<10}{texto:>{ancho}}"
return etiquetar
etiquetar_urgente = crear_etiquetador("[URGENTE]", 30)
etiquetar_nota = crear_etiquetador("[nota]", 24)
print(etiquetar_urgente("Cartel feria del libro"))
print(etiquetar_nota("Cartel feria del libro"))La función interna etiquetar lee prefijo y ancho del ámbito envolvente (la E de LEGB, 04-03) y sigue recordándolos después de que crear_etiquetador haya terminado. Compara este enfoque con pasar prefijo y ancho en cada llamada: la fábrica te ahorra repetirlos, y de paso deja claro que un etiquetador de urgencias es una cosa y uno de notas es otra.
Solución 3.
codigo = pedir_dato("Codigo de cliente: ",
lambda t: t.isdigit() and len(t) == 4,
"Deben ser exactamente cuatro digitos.")
respuesta = pedir_dato("Confirmas? (s/n): ",
lambda t: t.lower() in ("s", "n"),
"Responde s o n.")
def es_avance_valido(texto):
"""Comprueba que sea un entero de 0 a 100 multiplo de 5."""
if not texto.isdigit():
return False
valor = int(texto)
return 0 <= valor <= 100 and valor % 5 == 0
avance = int(pedir_dato("Avance (0-100): ", es_avance_valido,
"Entero de 0 a 100, multiplo de 5."))Los dos primeros criterios caben holgadamente en una expresión y se leen mejor dentro de la llamada, así que lambda. El tercero necesita convertir el texto a número después de comprobar que se puede convertir, y eso son dos pasos con una variable intermedia: imposible en una lambda legible, y además merece un docstring que explique la regla del múltiplo de 5. Ese if not texto.isdigit(): return False es, por cierto, la cláusula de guarda de 04-02 haciendo su trabajo: sin ella, int(texto) reventaría con un ValueError ante cualquier letra.
Conclusión del módulo
Las funciones también son datos. Sin paréntesis, un nombre de función es un valor de tipo function que se puede imprimir, asignar a otra variable y pasar por ahí; con paréntesis, es una llamada. De ahí salen las funciones de orden superior: las que reciben una función como parámetro —el esqueleto lo pones tú, el comportamiento concreto lo pone quien llama— y las que devuelven una función, las fábricas, capaces de crear versiones especializadas que recuerdan su configuración. Para las funciones diminutas de usar y tirar está lambda, limitada a una sola expresión y sin return explícito; para todo lo demás, def. Y el patrón callback —pasar la comprobación como argumento— ha unificado las tres funciones pedir_* de TareaFácil en una sola pedir_dato.
Con esto se cierra el módulo 4. Sabes definir y llamar funciones, y distinguir la definición de la llamada (04-01). Sabes darles parámetros posicionales, por nombre y con valores por defecto, y hacer que devuelvan uno o varios valores con return, entendiendo por qué devolver casi siempre gana a imprimir (04-02). Sabes dónde vive cada nombre gracias a la regla LEGB, por qué las globales cambiantes son un problema y por qué las constantes no lo son (04-03). Sabes descomponer un programa entero con criterios comprobables: una responsabilidad por función, niveles de abstracción homogéneos, entrada y salida separadas de la lógica, DRY sin sobre-fragmentar, y un main() protegido por if __name__ == "__main__": (04-04). Y sabes que las funciones son valores que se pasan y se devuelven (04-05).
tareafacil.py ha llegado a la versión 0.7: constantes arriba, funciones agrupadas por familias, un main() de veintidós líneas que se lee como un índice, cero duplicación y ocho piezas reutilizables en cualquier otro programa. Es un salto enorme respecto al muro de la v0.6.
Y sin embargo, sigue gestionando una sola tarea. Marta no puede tener a la vez el cartel de la feria del libro, el menú de la Panadería Solé y el encargo del cliente Vidal: registrar una destruye la anterior. Las seis variables sueltas —titulo, responsable, prioridad, dias, completada, hay_tarea— viajan juntas de función en función porque son, en realidad, una sola cosa sin nombre. Y mostrar_ficha(titulo, responsable, prioridad, dias, completada) con sus cinco parámetros es el síntoma más visible.
Las dos cosas —guardar muchas tareas y agrupar los datos de cada una— se resuelven con lo mismo: estructuras de datos. Es el módulo 5, que empieza con Listas y arreglos: la herramienta que permitirá por fin que TareaFácil gestione el trabajo real de Estudio Alba, recorrerlo con un for, buscar en él, ordenarlo por prioridad y, cuando lleguemos a los ficheros, que siga ahí mañana por la mañana.
Fundamentos de la Programación
Módulo 1: Introducción a la Programación
- ¿Qué es la programación?
- Historia de la programación
- Lenguajes de programación
- Entornos de desarrollo
- Del problema al algoritmo
Módulo 2: Conceptos Básicos
- Variables y tipos de datos
- Operadores y expresiones
- Entrada y salida de datos
- Conversión de tipos y validación de datos
Módulo 3: Estructuras de Control
Módulo 4: Funciones y Procedimientos
- Definición y uso de funciones
- Parámetros y retorno de valores
- Ámbito de variables
- Descomponer un programa en funciones
- Funciones como valores: lambda y orden superior
Módulo 5: Estructuras de Datos
- Listas y arreglos
- Cadenas de caracteres
- Diccionarios y conjuntos
- Tuplas y estructuras anidadas
- Guardar datos en archivos: texto, CSV y JSON
Módulo 6: Algoritmos Básicos
Módulo 7: Objetos y Organización del Código
- De los datos a los objetos: clases e instancias
- Atributos, métodos y constructor
- Colecciones de objetos
- Módulos, paquetes e importaciones
Módulo 8: Buenas Prácticas y Herramientas
- Documentación y comentarios
- Depuración y manejo de errores
- Control de versiones
- Pruebas automatizadas
- Estilo, legibilidad y refactorización
