En esta página
Funciones y scope
Definiendo funciones con def
Las funciones son los bloques de construcción fundamentales de cualquier programa Python. Se definen con la palabra clave def, seguida del nombre, los parámetros entre paréntesis y dos puntos. El cuerpo de la función se indenta:
def saludar(nombre: str) -> str:
"""Devuelve un saludo personalizado."""
return f"¡Hola, {nombre}!"
# Llamar a la función
mensaje = saludar("María")
print(mensaje) # ¡Hola, María!
# Las funciones son objetos de primera clase en Python
print(type(saludar)) # <class 'function'>
print(saludar) # <function saludar at 0x...>Docstrings: documentando tus funciones
Un docstring es una cadena de texto que documenta una función, clase o módulo. Va inmediatamente después de la definición y se accede con .__doc__ o la función help():
def calcular_imc(peso_kg: float, altura_m: float) -> float:
"""
Calcula el Índice de Masa Corporal (IMC).
El IMC se calcula dividiendo el peso en kilogramos
entre el cuadrado de la altura en metros.
Args:
peso_kg: Peso de la persona en kilogramos.
altura_m: Altura de la persona en metros.
Returns:
El valor del IMC redondeado a 2 decimales.
Raises:
ValueError: Si el peso o la altura son negativos o cero.
Example:
>>> calcular_imc(70, 1.75)
22.86
"""
if peso_kg <= 0 or altura_m <= 0:
raise ValueError("El peso y la altura deben ser positivos")
return round(peso_kg / (altura_m ** 2), 2)
# Acceder a la documentación
help(calcular_imc)
print(calcular_imc.__doc__)Parámetros por defecto
Los parámetros pueden tener valores por defecto. Los parámetros con valores por defecto deben ir después de los parámetros sin valor por defecto:
def crear_usuario(
nombre: str,
edad: int,
rol: str = "usuario",
activo: bool = True
) -> dict:
"""Crea un diccionario con los datos del usuario."""
return {
"nombre": nombre,
"edad": edad,
"rol": rol,
"activo": activo
}
# Llamadas válidas
u1 = crear_usuario("Ana", 25)
u2 = crear_usuario("Bruno", 32, "admin")
u3 = crear_usuario("Carmen", 28, "moderador", False)
u4 = crear_usuario("Diego", 35, activo=False) # Argumento keyword
print(u1) # {'nombre': 'Ana', 'edad': 25, 'rol': 'usuario', 'activo': True}
print(u4) # {'nombre': 'Diego', 'edad': 35, 'rol': 'usuario', 'activo': False}La trampa del valor por defecto mutable
Este es uno de los errores más comunes para programadores nuevos en Python:
# ¡MAL! El defecto mutable se comparte entre llamadas
def agregar_elemento_mal(elemento: str, lista: list = []) -> list:
lista.append(elemento)
return lista
print(agregar_elemento_mal("a")) # ['a']
print(agregar_elemento_mal("b")) # ['a', 'b'] — ¡debería ser ['b']!
print(agregar_elemento_mal("c")) # ['a', 'b', 'c'] — ¡definitivamente mal!
# BIEN — usa None como centinela
def agregar_elemento_bien(elemento: str, lista: list | None = None) -> list:
if lista is None:
lista = []
lista.append(elemento)
return lista
print(agregar_elemento_bien("a")) # ['a']
print(agregar_elemento_bien("b")) # ['b'] — correcto*args — Argumentos posicionales variables
El parámetro *args recibe cualquier cantidad de argumentos posicionales como una tupla:
def suma(*numeros: float) -> float:
"""Suma cualquier cantidad de números."""
total = 0.0
for n in numeros:
total += n
return total
print(suma(1, 2, 3)) # 6.0
print(suma(10, 20, 30, 40)) # 100.0
print(suma()) # 0.0
# También con la función integrada sum()
def suma_v2(*numeros: float) -> float:
return sum(numeros)
# Desempaquetar una lista como argumentos posicionales
numeros = [1, 2, 3, 4, 5]
print(suma(*numeros)) # 15.0 — el * desempaqueta la lista**kwargs — Argumentos keyword variables
El parámetro **kwargs recibe cualquier cantidad de argumentos keyword como un diccionario:
def mostrar_info(**datos: str) -> None:
"""Muestra pares clave-valor de información."""
for clave, valor in datos.items():
print(f" {clave.capitalize()}: {valor}")
mostrar_info(nombre="Elena", ciudad="Madrid", profesión="Desarrolladora")
# Nombre: Elena
# Ciudad: Madrid
# Profesión: Desarrolladora
# Combinar *args y **kwargs
def todo_junto(*args: str, **kwargs: str) -> None:
print(f"Posicionales: {args}")
print(f"Keyword: {kwargs}")
todo_junto("a", "b", "c", x="1", y="2")
# Posicionales: ('a', 'b', 'c')
# Keyword: {'x': '1', 'y': '2'}
# El orden es: posicionales, *args, keyword, **kwargs
def configurar(host: str, puerto: int = 8080, *args: str,
debug: bool = False, **opciones: str) -> None:
print(f"host={host}, puerto={puerto}, debug={debug}")
print(f"extra args: {args}")
print(f"opciones: {opciones}")Funciones lambda
Las funciones lambda son funciones anónimas de una sola expresión. Son útiles como argumentos de otras funciones:
# Función normal equivalente
def cuadrado(x: float) -> float:
return x ** 2
# Lambda equivalente
cuadrado_lambda = lambda x: x ** 2
print(cuadrado(5)) # 25
print(cuadrado_lambda(5)) # 25
# Usos comunes: como argumento de sorted(), map(), filter()
personas = [
{"nombre": "Carlos", "edad": 30},
{"nombre": "Ana", "edad": 25},
{"nombre": "Bruno", "edad": 35},
]
# Ordenar por edad
por_edad = sorted(personas, key=lambda p: p["edad"])
print(por_edad[0]["nombre"]) # Ana
# Ordenar por nombre (descendente)
por_nombre_desc = sorted(personas, key=lambda p: p["nombre"], reverse=True)
print(por_nombre_desc[0]["nombre"]) # Carlos
# map() — aplica función a cada elemento
numeros = [1, 2, 3, 4, 5]
cuadrados = list(map(lambda x: x ** 2, numeros))
print(cuadrados) # [1, 4, 9, 16, 25]
# filter() — filtra elementos según condición
pares = list(filter(lambda x: x % 2 == 0, numeros))
print(pares) # [2, 4]Scope y la regla LEGB
Python determina en qué ámbito buscar un nombre de variable siguiendo la regla LEGB:
- Local: dentro de la función actual
- Enclosing: en funciones contenedoras (para closures)
- Global: en el módulo (nivel de script)
- Built-in: nombres integrados como
print,len,range
x = "global" # Ámbito global
def exterior():
x = "enclosing" # Ámbito enclosing
def interior():
x = "local" # Ámbito local
print(x) # Busca en L primero → "local"
interior()
print(x) # Ámbito enclosing → "enclosing"
exterior()
print(x) # Ámbito global → "global"Las palabras clave global y nonlocal
contador = 0 # Variable global
def incrementar() -> None:
global contador # Declara que usamos la variable global
contador += 1
incrementar()
incrementar()
print(contador) # 2
def hacer_contador(inicio: int = 0):
"""Devuelve funciones para manipular un contador local."""
cuenta = inicio # Variable en el ámbito enclosing
def incrementar_local(paso: int = 1) -> None:
nonlocal cuenta # Accede a la variable del ámbito enclosing
cuenta += paso
def obtener() -> int:
return cuenta
return incrementar_local, obtener
inc, get = hacer_contador(10)
inc()
inc(5)
print(get()) # 16Closures
Una closure (clausura) es una función que "recuerda" el entorno en el que fue creada, incluso después de que la función exterior haya terminado de ejecutarse:
def multiplicador(factor: float):
"""Fábrica de funciones multiplicadoras."""
def multiplicar(numero: float) -> float:
return numero * factor # 'factor' viene del ámbito enclosing
return multiplicar
doble = multiplicador(2)
triple = multiplicador(3)
decima_parte = multiplicador(0.1)
print(doble(5)) # 10.0
print(triple(5)) # 15.0
print(decima_parte(50)) # 5.0
# Puedes ver las variables capturadas por una closure
print(doble.__closure__) # (<cell at 0x...>)
print(doble.__closure__[0].cell_contents) # 2
def crear_validador(minimo: float, maximo: float):
"""Crea una función validadora de rango."""
def validar(valor: float) -> bool:
return minimo <= valor <= maximo
return validar
es_porcentaje = crear_validador(0, 100)
es_temperatura = crear_validador(-273.15, 1_000_000)
print(es_porcentaje(75)) # True
print(es_porcentaje(150)) # False
print(es_temperatura(-300)) # False
print(es_temperatura(100)) # TrueFunciones como objetos de primera clase
En Python, las funciones son objetos de primera clase. Pueden almacenarse en variables, pasarse como argumentos y devolverse como valores de retorno:
from typing import Callable
def aplicar(func: Callable[[int], int], valor: int) -> int:
"""Aplica una función a un valor."""
return func(valor)
def cuadrado(x: int) -> int:
return x ** 2
def cubo(x: int) -> int:
return x ** 3
print(aplicar(cuadrado, 5)) # 25
print(aplicar(cubo, 3)) # 27
# Lista de funciones
operaciones: list[Callable[[int], int]] = [cuadrado, cubo, lambda x: x + 1]
numero = 4
for op in operaciones:
print(op(numero)) # 16, 64, 5Resumen
Las funciones en Python son extremadamente flexibles: *args y **kwargs permiten una firma variádica, las lambdas son ideales para expresiones cortas como argumentos, y las closures permiten crear funciones que mantienen estado. La regla LEGB explica cómo Python resuelve los nombres de variables. En la próxima lección exploraremos las listas y tuplas, las estructuras de datos secuenciales más utilizadas.
Inicia sesión para guardar tu progreso