Imagina que construyes un robot complejo 🤖; en vez de forjar cada tornillo desde cero, diseñas piezas reutilizables. Las funciones son exactamente eso en programación: componentes auto‑contenidos que realizan una tarea específica y que puedes invocar una y otra vez.
Escribe una vez y úsalo mil.
Divide y vencerás.
Más fácil de depurar 👓.
Usa la función sin conocer sus componentes internos.
Analogía: la fórmula de energía cinética E_k = ½·m·v² se convierte en energia_cinetica(m,v).
Para definir una función usa def + nombre + paréntesis y dos puntos. Todo el cuerpo va indentado cuatro espacios.
def saludo_laboratorio():
print("🔬 ¡Bienvenido al laboratorio virtual!")
print("Por favor, sigue los protocolos de seguridad.")
saludo_laboratorio()
print("--- Iniciando experimento 1 ---")
saludo_laboratorio()
Los parámetros son los nombres declarados en la definición de la función; los argumentos son los valores reales que envías al invocarla.
| Aspecto | Parámetro | Argumento |
|---|---|---|
| Contexto | Definición (def) | Llamada |
| Naturaleza | Nombre | Valor concreto |
def calcular_area_rectangulo(base, altura):
return base * altura
print(calcular_area_rectangulo(10, 5))
print(calcular_area_rectangulo(altura=3, base=7))
Cuidado: los parámetros viven sólo dentro de la función. Cambiarlos no modifica la variable original (salvo que sean mutables y compartidos).
returnreturn detiene la ejecución de la función y envía un resultado al punto de llamada. Si se omite, Python devuelve None.
Todo lo que esté después de return no se ejecuta.
Puedes devolver uno o varios valores (en forma de tupla implícita).
*args (de variable argument) permite a una función
recibir un número arbitrario de argumentos posicionales.
Internamente, todo lo que sobrepase los parámetros fijos se almacena
en una tuple. Gracias a ello, tu función puede ser tan
flexible como un plot twist en una serie 📺.
*args
| Elemento | Significado |
|---|---|
* |
Operador de unpacking; atrapa el “resto” de los argumentos. |
args |
Convención de nombre – pero podrías usar *nums, *files, etc.
|
| Tipo resultante | tuple inmutable. |
| Posición en la firma |
Después de los parámetros posicionales “normales” y antes de **kwargs.
|
def sumar(*numeros):
"""Devuelve la suma de todos los argumentos."""
return sum(numeros)
print(sumar(2, 4, 6)) # → 12
print(sumar(1, 2, 3, 4, 5)) # → 15
def registrar_evento(etiqueta, *detalles):
texto = f"[{etiqueta}]" + ": " + ", ".join(map(str, detalles))
print(texto)
registrar_evento("LOGIN", 42, "usuario", "OK")
# [LOGIN]: 42, usuario, OK
def promedio(*vals):
return sum(vals) / len(vals) if vals else 0
def promedio_redondeado(*datos):
return round(promedio(*datos), 2)
print(promedio_redondeado(7.5, 8.1, 9.3))
Usa *nombres, *puntos, etc.,
cuando el contexto mejore la legibilidad.
def f(a, b=0, *args, **kwargs).
Nada puede ir después de *args salvo **kwargs.
Comprueba tipos o cantidad mínima:
if len(args) < 2: raise ValueError.
Si tu función siempre espera 3 valores, declara 3 parámetros.
*args es para casos realmente variables.
Dominar *args multiplica la versatilidad de tus APIs y facilita la creación de wrapper-functions y logging detallado.
Si *args captura “todos los sobrantes posicionales”,
**kwargs (de “keyword arguments”) hace lo propio con los
argumentos nombrados. El operador ** empaqueta cada par
clave=valor adicional en un dict, ideal para funciones con
configuraciones opcionales, objetos heterogéneos o parámetros
evolutivos.
**kwargs
| Elemento | Significado |
|---|---|
** |
Operador de double-unpacking; captura pares nombre=valor.
|
kwargs |
Convención de nombre (“kw” = keyword).
Puedes usar **opciones, **params, etc.
|
| Tipo resultante | dict mutable. |
| Posición en la firma | Siempre el último parámetro de la lista. |
def crear_perfil(nombre, **extra):
perfil = {"nombre": nombre}
perfil.update(extra)
return perfil
print(crear_perfil("Ana",
edad=17,
ciudad="Bogotá",
pasatiempos=["ajedrez", "música"]))
def procesar_datos(accion, *datos, **config):
modo = config.get("modo", "debug")
if accion == "suma":
res = sum(datos)
elif accion == "promedio":
res = sum(datos)/len(datos) if datos else 0
else:
raise ValueError("Acción no soportada")
return round(res, config.get("decimales", 2)) if modo=="prod" else res
print(procesar_datos("suma", 2, 4, 8, decimales=0, modo="prod"))
def imprimir_tabla(datos, **op):
ancho = op.get("ancho", 10)
for fila in datos:
print("|".join(str(c).ljust(ancho) for c in fila))
def mostrar_personas(*filas, **estilo):
imprimir_tabla(filas, **estilo) # forward
mostrar_personas(
("ID", "Nombre"),
(1, "Luisa"),
(2, "Carlos"),
ancho=12)
Lista las claves aceptadas en el docstring o lanza
TypeError si llega algo inesperado.
Convierte tipos (ej. int()) o establece
límites antes de usar las opciones.
Si **kwargs se vuelve gigantesco, crea sub-diccionarios
(db_opts, ui_opts…) y pásalos explícitamente.
El poder de **kwargs trae consigo la tentación de
esconder demasiada lógica. Mantén transparencia ⚖️.
Controlar **kwargs te brinda APIs evolutivas sin romper compatibilidad. Úsalo estratégicamente y tus funciones crecerán con las necesidades de tu proyecto.
Un parámetro con valor por defecto le dice a Python:
“si el llamador no envía nada, usa este plan B”.
Esto simplifica firmas, evita sobrecarga de None-checks y
crea API más amistosas.
📏 Orden canónico:
posicionales / obligatorios → opcionales → *args → **kwargs.
Alterarlo provoca SyntaxError.
def saludar(nombre, saludo="Hola"):
print(f"{saludo}, {nombre}!")
saludar("María") # Hola, María!
saludar("Luis", saludo="Buenos días") # Buenos días, Luis!
Los objetos mutables (listas, diccionarios, conjuntos) se evalúan una sola vez, en tiempo de definición de la función. Todos los llamadores compartirán la misma instancia.
Usa None como centinela y crea un nuevo objeto dentro.
Patrón conocido como “None factory”.
def agregar_item_peligroso(item, lista=[]): # 👎 NO HAGAS ESTO
lista.append(item)
return lista
print(agregar_item_peligroso(1)) # [1]
print(agregar_item_peligroso(2)) # [1, 2] ¡Sorpresa!
def agregar_item_seguro(item, lista=None):
if lista is None:
lista = []
lista.append(item)
return lista
print(agregar_item_seguro(1)) # [1]
print(agregar_item_seguro(2)) # [2]
verbose=False en vez de 0).timeout=30 habla por sí mismo.DEFAULT_PORT) o enumeraciones.Dominar los valores por defecto te permite diseñar APIs limpias y evolutivas sin sobrecargar al usuario con parámetros obligatorios. ¡Dale a tu código esa dosis de ergonomía!
Una expresión lambda define una función
pequeña, sin nombre y de una sola línea.
Se usa donde escribir def completo sería excesivo
(callbacks, ordenamientos, mapeos rápidos, etc.).
| Componente | Descripción |
|---|---|
lambda |
Palabra clave que inicia la expresión. |
| parámetros | Cero o más, separados por comas (pueden usar *args/**kwargs). |
: |
Separador entre parámetros y cuerpo. |
| expresión | Debe ser una única expresión (no return). |
palabras = ["árbol", "sol", "enciclopedia", "luz"]
palabras.sort(key=lambda w: len(w))
print(palabras) # ['sol', 'luz', 'árbol', 'enciclopedia']
numeros = [1, 2, 3, 4]
cuadrados = list(map(lambda x: x**2, numeros))
print(cuadrados) # [1, 4, 9, 16]
lambda
Ideal para funciones de paso rápido — evita un def innecesario.
Úsalas con moderación; expresiones largas o lógicas complejas merecen un def.
No puedes documentarlas directamente; para APIs públicas prefiere funciones nombradas.
Las lambdas anidadas crean tracebacks menos claros; nómbralas si algo puede fallar.
Dominar lambda te permitirá escribir código conciso en operaciones de alto orden (map, filter, GUI callbacks, ordenamientos complejos). Úsalas como condimento, no como plato principal.
Las expresiones lambda brillan cuando encapsulan fórmulas
cortas y repetitivas. Combinadas con bloques try / except
logras prototipos robustos en una sola pasada 🏎️.
import math
area_circ = lambda r: math.pi * r**2 # A = π·r²
perim_circ = lambda r: 2 * math.pi * r # P = 2πr
radio = 3
print(f"Área: {area_circ(radio):.2f}")
print(f"Perímetro: {perim_circ(radio):.2f}")
E_p = lambda m, h, g=9.81: m * g * h
datos = [(1.5, 2), (0.8, 5), (10, 0.3)] # (kg, m)
energias = list(map(lambda p: round(E_p(*p), 2), datos))
print(energias) # [29.43, 39.24, 29.43]
m = lambda x1, y1, x2, y2: (y2 - y1) / (x2 - x1)
def pendiente_segura(p1, p2):
try:
return m(*p1, *p2)
except ZeroDivisionError:
return math.inf # Recta vertical
print(pendiente_segura((0, 0), (2, 4))) # 2.0
print(pendiente_segura((3, 1), (3, 10))) # inf
R = lambda V, I: V / I
def resistencia_segura(volts, amps):
try:
return round(R(volts, amps), 2)
except ZeroDivisionError:
return "∞ (aislamiento)"
print(resistencia_segura(12, 0.5), "Ω") # 24.0 Ω
print(resistencia_segura(5, 0), "Ω") # ∞ (aislamiento) Ω
dist_euclid,
filtro_par — ganarás trazabilidad.try / except dentro de funciones wrapper,
no en la lambda, para mantenerla limpia.(lambda a,b: (a+b)/2) ✅,
(lambda a,b: (a+b)/(c+d)-e*f**g) ❌ (mejor def).
Mezclar lambdas minimalistas con un sólido
try / except te permite pasar de la idea al experimento
en segundos, sin sacrificar estabilidad. ¡Aplícalo y acelera tu
próximo proyecto científico!