Tabla de Contenidos
Funciones y descomposición del código
Encapsular lógica reutilizable.
Este módulo forma parte del curso de programación desde cero con Python
El programa que repite la misma receta
Imagina que estás escribiendo un programa para una tienda y necesitas calcular el precio final con IVA en tres lugares distintos: al mostrar un producto, al generar una factura y al calcular el total de un carrito. Sin funciones, copiarías la fórmula precio * (1 + iva) tres veces. Si mañana el IVA cambia, tendrías que recordar modificar las tres copias; si olvidas una, el programa dará resultados inconsistentes. Una función te permite escribir la fórmula una sola vez, darle un nombre (calcular_precio_con_iva) y llamarla desde los tres lugares. Si el IVA cambia, modificas una sola línea.
Eso es la descomposición: dividir un problema grande en piezas pequeñas, cada una con un nombre claro y una responsabilidad única. Un programa bien descompuesto es más fácil de leer, de probar, de corregir y de ampliar. Las funciones son el vehículo principal de esa descomposición en Python.
¿Qué es una función y por qué usarla?
Una función es un bloque de código con un nombre, que realiza una tarea concreta y puede recibir datos (argumentos) y devolver un resultado. Se define con la palabra clave def y se llama escribiendo su nombre seguido de paréntesis:
def saludar(): print("Hola, mundo") saludar() # Llamada: ejecuta el bloque
Ventajas inmediatas:
- Reutilización: escribes la lógica una vez y la usas muchas.
- Legibilidad: un nombre descriptivo (calcular_media) explica qué hace el bloque mejor que el código crudo.
- Mantenimiento: si la lógica cambia, modificas un solo lugar.
- Pruebas: puedes probar la función de forma aislada (Módulo 17).
[PYTHON] En Python, las funciones son objetos de primera clase: puedes asignarlas a variables, pasarlas como argumentos y devolverlas desde otras funciones. Esta característica, que en C o Java requiere mecanismos más elaborados (punteros a función, interfaces), es natural en Python y se explotará en el Módulo 15 con decoradores y funciones de orden superior.
Parámetros y argumentos
Los parámetros son los nombres que aparecen en la definición de la función. Los argumentos son los valores que se pasan en la llamada.
def saludar(nombre): # 'nombre' es un parámetro print(f"Hola, {nombre}") saludar("Ana") # "Ana" es un argumento
La terminología importa poco al principio, pero conviene usarla con precisión: un parámetro es la variable de la definición; un argumento es el valor concreto de una llamada.
return: devolver un valor
Una función puede devolver un resultado con return. La ejecución de la función termina en el momento en que se ejecuta return, y el valor devuelto se convierte en el resultado de la llamada.
def sumar(a, b): return a + b resultado = sumar(3, 4) print(resultado) # 7
Si una función no tiene return, o tiene un return sin valor, devuelve None.
[ERROR HABITUAL] Confundirprint()conreturn.print()muestra un valor en la pantalla, pero no lo devuelve.returndevuelve un valor al código que llamó a la función, pero no lo muestra. Una función que haceprint(resultado)en lugar dereturn resultadono permite que el programa use ese resultado para nada más. Esta es una de las confusiones más importantes del módulo.
# Incorrecto: imprime pero no devuelve nada útil def sumar_mal(a, b): print(a + b) # Muestra 7, pero devuelve None # Correcto: devuelve el valor para que pueda usarse def sumar_bien(a, b): return a + b
Argumentos posicionales y por palabra clave
Python permite llamar a una función de dos formas:
- Argumentos posicionales: se asignan a los parámetros en el orden en que aparecen.
- Argumentos por palabra clave (keyword arguments): se especifica el nombre del parámetro (parametro=valor), y el orden no importa.
def describir(nombre, edad): print(f"{nombre} tiene {edad} años") describir("Ana", 25) # Posicional describir(edad=25, nombre="Ana") # Por palabra clave
Reglas de orden: en una llamada, los argumentos posicionales deben ir antes que los argumentos por palabra clave. No puedes pasar un posicional después de uno nominal.
[ERROR HABITUAL] Pasar el mismo argumento dos veces:describir("Ana", nombre="Luis")produceTypeError: got multiple values for argument 'nombre'. Cada parámetro recibe exactamente un valor.
[BUENA PRÁCTICA] Usa argumentos por palabra clave cuando la llamada no sea evidente por sí sola, especialmente si la función tiene varios parámetros del mismo tipo.crear_usuario("Ana", True, False)es críptico;crear_usuario(nombre="Ana", admin=True, activo=False)es autoexplicativo.
Valores predeterminados y el peligro de los mutables
Puedes asignar un valor predeterminado a un parámetro en la definición. Si la llamada no proporciona ese argumento, se usa el valor predeterminado:
def saludar(nombre, saludo="Hola"): print(f"{saludo}, {nombre}") saludar("Ana") # Hola, Ana saludar("Ana", "Hey") # Hey, Ana
[ERROR HABITUAL] El error clásico: usar un objeto mutable (lista, diccionario, conjunto) como valor predeterminado. El valor predeterminado se evalúa una sola vez, cuando se define la función, no en cada llamada. Si lo mutas, el cambio persiste entre llamadas.
# Incorrecto: la lista se comparte entre todas las llamadas def agregar_elemento(elemento, lista=[]): lista.append(elemento) return lista print(agregar_elemento(1)) # [1] print(agregar_elemento(2)) # [1, 2] ¡Sorpresa!
La solución idiomática es usar None como predeterminado y crear un objeto nuevo dentro de la función:
def agregar_elemento(elemento, lista=None): if lista is None: lista = [] lista.append(elemento) return lista
[PYTHON] Python 3.15 introduce un tipo incorporado llamadosentinel(PEP 661) para crear valores centinela únicos. Sirve para distinguir “no se proporcionó el argumento” de “se proporcionóNoneexplícitamente”. Es útil cuandoNonees un valor válido para el parámetro. Puedes crear un centinela conMISSING = sentinel("MISSING")y usarlo como predeterminado. No es imprescindible para empezar, pero es la herramienta moderna para este problema.
Ámbito de variables: local, global y no local
Las variables definidas dentro de una función tienen ámbito local: solo existen mientras la función se ejecuta y no son accesibles desde fuera.
def funcion(): x = 10 # Variable local print(x) funcion() # print(x) # NameError: x no existe fuera de la función
Una función puede leer variables globales (definidas fuera), pero no puede modificarlas sin declararlas explícitamente con global. Lo mismo ocurre con las variables de una función externa, que requieren nonlocal.
[BUENA PRÁCTICA] Evitaglobalynonlocalsalvo que sea estrictamente necesario. Las funciones que dependen de estado global son difíciles de probar y de razonar. Prefiere pasar los datos como argumentos y devolver resultados. El Zen de Python lo resume: “Explícito es mejor que implícito”.
Funciones puras y efectos secundarios
Una función pura cumple dos condiciones:
1. Dado el mismo conjunto de argumentos, siempre devuelve el mismo resultado. 2. No modifica el estado del programa (no imprime, no escribe archivos, no muta argumentos mutables).
# Función pura def cuadrado(x): return x * x # Función con efecto secundario (imprime) def mostrar_cuadrado(x): print(x * x)
Las funciones puras son más fáciles de probar, de componer y de razonar. Los efectos secundarios (imprimir, modificar una lista, escribir un archivo) no son malos en sí mismos, pero deben ser conscientes y controlados. Una buena práctica es separar la lógica de cálculo (pura) de la interacción con el exterior.
[PYTHON] El concepto de función pura proviene de la programación funcional, pero es aplicable a cualquier lenguaje. En Python, las comprensiones y funciones comosorted()osum()favorecen un estilo más funcional; las funciones con efectos secundarios son necesarias para la entrada/salida y la interacción con el usuario.
Docstrings: documentar funciones
Una docstring es una cadena literal que aparece como primera instrucción dentro de una función (o módulo, o clase). Sirve para documentar qué hace la función, qué parámetros recibe y qué devuelve. PEP 257 establece las convenciones:
- Usa siempre triple comilla doble (""").
- La primera línea es un resumen de una línea, que termina en punto y describe la acción (“Devuelve…”, “Calcula…”).
- Si hay más detalle, deja una línea en blanco y añade la descripción ampliada.
def calcular_media(notas): """Devuelve la media aritmética de una lista de notas. Args: notas: Una lista de números (int o float). Returns: La media como float. Si la lista está vacía, devuelve 0. """ if not notas: return 0 return sum(notas) / len(notas)
[BUENA PRÁCTICA] Escribe docstrings en todas las funciones públicas. Herramientas comohelp()y generadores de documentación las usan automáticamente. Una docstring útil explica el propósito y el contrato de la función, no repite el código.
*args, **kwargs y argumentos solo por palabra clave
A veces no sabes de antemano cuántos argumentos recibirá una función. Python ofrece dos mecanismos:
- *args recoge los argumentos posicionales sobrantes en una tupla.
- **kwargs recoge los argumentos por palabra clave sobrantes en un diccionario.
def imprimir_todo(*args, **kwargs): print("Posicionales:", args) print("Nominales:", kwargs) imprimir_todo(1, 2, 3, nombre="Ana", edad=25) # Posicionales: (1, 2, 3) # Nominales: {'nombre': 'Ana', 'edad': 25}
Los parámetros después de * (o después de *args) son solo por palabra clave (keyword-only): no pueden pasarse por posición.
def configurar(*, modo="auto", verbose=False): print(modo, verbose) configurar(modo="manual") # Correcto # configurar("manual") # TypeError: solo acepta argumentos por palabra clave
[ERROR HABITUAL] Colocar**kwargsantes de*argsen la definición. El orden correcto es: parámetros normales,*args, parámetros solo por palabra clave,**kwargs.*argsdebe ir antes que**kwargs.
Funciones lambda
Una función lambda es una función anónima, definida en una sola expresión. Su sintaxis es lambda parámetros: expresión. Devuelve el valor de la expresión.
cuadrado = lambda x: x * x print(cuadrado(5)) # 25 # Equivalente con def def cuadrado_def(x): return x * x
Las lambda son útiles para operaciones simples que se pasan como argumento a otra función, como sorted(clave=lambda x: x[1]) o map(lambda x: x * 2, lista).
[BUENA PRÁCTICA] No abuses delambda. Si la lógica requiere más de una expresión, nombres descriptivos o varias líneas, usadef. Una lambda solo debe usarse cuando la claridad no se resiente. PEP 8 recomienda no asignar lambda a un nombre (cuadrado = lambda x: ...), precisamente porquedefes más legible.
6. Ejemplos comentados
Ejemplo 1: Función pura con docstring.
def es_par(numero): """Devuelve True si el número es par, False en caso contrario.""" return numero % 2 == 0 print(es_par(4)) # True print(es_par(7)) # False
Seguimiento: La función no imprime, solo devuelve. Es pura: para el mismo numero, siempre devuelve el mismo resultado. La docstring sigue PEP 257: una línea, en imperativo, con punto final.
Ejemplo 2: Función con valor predeterminado y None seguro.
def crear_lista(elementos=None): """Crea una lista a partir de un iterable, o una lista vacía si es None.""" if elementos is None: return [] return list(elementos) print(crear_lista()) # [] print(crear_lista([1, 2, 3])) # [1, 2, 3]
Predicción: ¿Qué pasaría si el predeterminado fuera elementos=[]? La misma lista se reutilizaría entre llamadas, acumulando elementos. El uso de None evita ese error.
Ejemplo 3: *args y **kwargs.
def resumen(titulo, *datos, **opciones): print(f"== {titulo} ==") for dato in datos: print("-", dato) for clave, valor in opciones.items(): print(f"{clave}: {valor}") resumen("Informe", "línea 1", "línea 2", autor="Ana", fecha="2026-10-29")
Seguimiento: titulo es un parámetro normal; *datos captura los posicionales sobrantes; **opciones captura los nominales sobrantes.
7. Seguimiento de la ejecución
Cuando Python encuentra una definición def, no ejecuta el cuerpo: crea un objeto función y asocia el nombre a ese objeto. El cuerpo se ejecuta solo cuando la función se llama.
En una llamada, Python sigue estos pasos:
1. Evalúa los argumentos de la llamada.
2. Crea un nuevo ámbito local (un namespace).
3. Asigna los argumentos a los parámetros correspondientes (por posición o por nombre).
4. Ejecuta el cuerpo hasta encontrar return o hasta el final.
5. Devuelve el valor de return (o None si no hay return).
6. Destruye el ámbito local.
Este ciclo se repite en cada llamada. Las variables locales de una llamada no interfieren con las de otra.
8. Errores habituales
| Error | Causa | Cómo diagnosticarlo |
|---|---|---|
TypeError: missing required positional argument | La llamada no proporciona un argumento obligatorio. | Revisa la firma de la función y el número de argumentos. |
TypeError: got multiple values for argument | Se pasa el mismo parámetro por posición y por nombre. | No mezcles posicional y nominal para el mismo parámetro. |
TypeError: takes 0 positional arguments but 1 was given | La función no acepta argumentos, pero se le pasa uno. | Revisa la definición; añade parámetros o elimina el argumento. |
| Valor predeterminado mutable compartido | def f(lista=[]) comparte la lista entre llamadas. | Usa None como predeterminado y crea el objeto dentro. |
NameError al usar una variable local fuera de la función | La variable solo existe en el ámbito local. | Pasa el valor como argumento o devuélvelo. |
UnboundLocalError al modificar una global sin global | Python interpreta la asignación como local. | Declara global (con moderación) o pasa el valor como argumento. |
SyntaxError en lambda con varias expresiones | lambda solo admite una expresión. | Usa def si necesitas varias líneas o instrucciones. |
[ERROR HABITUAL] El error de “missing required positional argument” suele ocurrir al olvidar un argumento en una llamada. El mensaje de Python 3.15 indica exactamente qué argumento falta, lo que facilita la corrección.
9. Particularidades de Python frente a otros lenguajes
- Funciones como objetos: en Python, una función es un objeto que puedes asignar a variables, pasar como argumento y devolver. En Java o C, esto requiere interfaces, punteros a función o reflexión.
- Argumentos por palabra clave: Python permite llamar a cualquier función con nombre=valor, algo que en C no existe y en Java solo está disponible en algunos APIs. Esto mejora la legibilidad de las llamadas.
- Valores predeterminados mutables: el comportamiento de evaluar el predeterminado una sola vez es una fuente clásica de errores. En otros lenguajes, los valores predeterminados se evalúan en cada llamada; en Python, no.
- *args y **kwargs: permiten una flexibilidad que en C requiere varargs (...) y en Java no existe de forma nativa. Python los integra en la sintaxis.
- Docstrings: son una convención cultural fuerte en Python, respaldada por herramientas como Sphinx o pydoc. En otros lenguajes, la documentación suele ir en comentarios separados.
10. Buenas prácticas
Incorrecto (función que hace demasiado y no devuelve):
def procesar(lista): total = 0 for x in lista: total += x print("La suma es", total) print("La media es", total / len(lista))
Mejor (funciones pequeñas, puras y con nombres claros):
def calcular_suma(numeros): """Devuelve la suma de una secuencia de números.""" return sum(numeros) def calcular_media(numeros): """Devuelve la media aritmética, o 0 si la secuencia está vacía.""" if not numeros: return 0 return calcular_suma(numeros) / len(numeros)
[BUENA PRÁCTICA] Cada función debe tener una sola responsabilidad. Si una función calcula, imprime y guarda en un archivo, está haciendo tres cosas; divídela. Las funciones cortas (idealmente menos de 20 líneas) son más fáciles de leer, probar y reutilizar. Y documenta con docstrings siguiendo PEP 257.
11. Windows y Linux
Las funciones son idénticas en Windows y Linux. La sintaxis, el ámbito, los argumentos y las docstrings se comportan igual. No hay diferencias operativas en este módulo.
[WINDOWS / LINUX] Si ejecutas un script que define funciones y lo importas desde otro script (Módulo 12), el comportamiento es el mismo en ambos sistemas. La portabilidad de las funciones es total.
12. Ejercicios guiados
Ejercicio guiado 1: Función pura con docstring.
Escribe una función es_mayor_de_edad(edad) que devuelva True si edad >= 18 y False en caso contrario. Incluye una docstring de una línea. Prueba con 17, 18 y 25.
Ejercicio guiado 2: Valor predeterminado seguro.
Escribe una función crear_diccionario(claves, valor=None) que cree un diccionario con las claves dadas y el mismo valor para todas. Si valor es None, usa 0. Evita el error de compartir un diccionario mutable entre llamadas.
Ejercicio guiado 3: *args y **kwargs.
Escribe una función mostrar_info(*args, **kwargs) que imprima los argumentos posicionales en una línea y los nominales en líneas separadas con el formato clave: valor.
13. Ejercicios autónomos
1. Predicción: ¿Qué muestra este código? Razona antes de ejecutar.
```python
def f(x):
return x * 2
print(f(3))
print(f("a"))
```
2. Diferencia print/return: Explica por qué esta función no permite reutilizar el resultado:
```python
def sumar(a, b):
print(a + b)
```
Reescríbela para que devuelva el valor.
3. Ámbito: ¿Qué ocurre al ejecutar este código? ¿Por qué?
```python
x = 10
def cambiar():
x = 20
cambiar()
print(x)
```
4. Argumentos por palabra clave: Define una función crear_usuario(nombre, edad, admin=False, activo=True) y llámala de tres formas distintas usando argumentos por palabra clave.
5. Reto opcional: Investiga el nuevo tipo sentinel de Python 3.15 y escribe una función buscar(lista, valor, predeterminado=sentinel("NO_ENCONTRADO")) que devuelva el índice del valor o el centinela si no está. Comprueba que el centinela es distinto de None.
14. Soluciones razonadas
Solución al ejercicio guiado 1:
def es_mayor_de_edad(edad): """Devuelve True si la edad es 18 o más.""" return edad >= 18 print(es_mayor_de_edad(17)) # False print(es_mayor_de_edad(18)) # True print(es_mayor_de_edad(25)) # True
Solución al ejercicio guiado 2:
def crear_diccionario(claves, valor=None): """Crea un diccionario con las claves dadas y el mismo valor.""" if valor is None: valor = 0 return {clave: valor for clave in claves}
Solución al ejercicio guiado 3:
def mostrar_info(*args, **kwargs): print("Posicionales:", ", ".join(str(a) for a in args)) for clave, valor in kwargs.items(): print(f"{clave}: {valor}")
Solución al ejercicio 1 (predicción): f(3) devuelve 6; f("a") devuelve "aa". La función no impone tipos: * funciona con números (multiplicación) y con cadenas (repetición). Este es un ejemplo de tipado dinámico: la misma función se adapta al tipo del argumento, para bien o para mal.
Solución al ejercicio 3 (ámbito): Muestra 10. La asignación x = 20 dentro de cambiar crea una variable local x que no afecta a la global. Para modificar la global, habría que declarar global x dentro de la función, pero eso se considera mala práctica en la mayoría de los casos.
Solución al reto opcional (orientativa):
from sentinel import sentinel # En 3.15 es un builtin, no requiere import NO_ENCONTRADO = sentinel("NO_ENCONTRADO") def buscar(lista, valor, predeterminado=NO_ENCONTRADO): try: return lista.index(valor) except ValueError: return predeterminado print(buscar([1, 2, 3], 2)) # 1 print(buscar([1, 2, 3], 5) is NO_ENCONTRADO) # True
El centinela permite distinguir “no encontrado” de un None que podría ser un valor válido en la lista.
15. Resumen
- Una función encapsula lógica reutilizable. Se define con def y se llama por su nombre.
- Parámetros son los nombres en la definición; argumentos son los valores en la llamada.
- return devuelve un valor; print() solo muestra. No los confundas.
- Los argumentos pueden pasarse por posición o por palabra clave (nombre=valor).
- Los valores predeterminados se evalúan una sola vez: nunca uses listas o diccionarios mutables como predeterminados. Usa None o el nuevo sentinel (3.15).
- El ámbito local aísla las variables de la función; global y nonlocal permiten modificarlas, pero deben evitarse.
- Las funciones puras devuelven siempre lo mismo y no tienen efectos secundarios; son más fáciles de probar.
- Las docstrings (PEP 257) documentan la función y son accesibles con help().
- *args captura posicionales en una tupla; **kwargs captura nominales en un diccionario. Los parámetros después de * son solo por palabra clave.
- Las lambda son funciones anónimas de una sola expresión; úsalas solo cuando la claridad no se resienta.
16. Autoevaluación
1. ¿Cuál es la diferencia entre print() y return? Pon un ejemplo en el que la diferencia cambie el comportamiento del programa.
2. ¿Por qué def f(lista=[]) es peligroso? ¿Cómo se corrige?
3. ¿Qué significa que una función sea pura? ¿Es print() una función pura?
4. ¿Para qué sirven *args y **kwargs? ¿En qué orden deben aparecer en la definición?
5. Reto: Sin ejecutar, predice qué muestra este código y explica el motivo:
```python
def f(a, b=2, *args, c=3, **kwargs):
return a, b, args, c, kwargs
print(f(1, 4, 5, 6, c=7, d=8)) ```
17. Fuentes oficiales
- Tutorial oficial: Defining Functions: https://docs.python.org/3.15/tutorial/controlflow.html#defining-functions
- Tutorial oficial: More on Defining Functions (valores predeterminados, argumentos por palabra clave, *args, **kwargs, parámetros especiales): https://docs.python.org/3.15/tutorial/controlflow.html#more-on-defining-functions
- Referencia del lenguaje: Calls (argumentos posicionales y por palabra clave): https://docs.python.org/3.15/reference/expressions.html#calls
- PEP 257 – Docstring Conventions: https://peps.python.org/pep-0257/
- PEP 8 – Style Guide for Python Code (nombres de funciones, argumentos): https://peps.python.org/pep-0008/
- PEP 661 – Sentinel Values (nuevo en Python 3.15): https://peps.python.org/pep-0661/
- Novedades de Python 3.15 (sentinel builtin): https://docs.python.org/3/whatsnew/3.15.html
