Saltar a contenido

1.14 · Biblioteca estándar

Objetivos. Al terminar este capítulo podrás: localizar una solución en la biblioteca estándar antes de programarla desde cero; trabajar con fechas, CSV y contadores; y seleccionar módulos adecuados para una tarea sin añadir dependencias innecesarias. Como ampliación: componer iteradores con itertools y reutilizar funciones con functools.

Evidencia de logro. Construirás un informe reproducible a partir de datos de ventas que use módulos estándar para fechas, CSV y conteos, explicando qué parte del problema resuelve cada módulo.

Contexto y motivación

Python incluye una colección amplia de módulos que se instala junto con el intérprete. Es la biblioteca estándar. Antes de buscar un paquete externo conviene comprobar si la solución que necesitas ya existe: reduces dependencias, instalación y superficie de mantenimiento.

Este capítulo reúne herramientas que aparecen con frecuencia al construir scripts y aplicaciones. pathlib y re ya se introdujeron en 1.9 y 1.10; aquí se combinan con otros módulos en un flujo completo.

Vocabulario

Término Significado
Biblioteca estándar Módulos incluidos con Python
CSV Formato tabular separado por delimitadores
Counter Contador especializado de elementos
Iterador Objeto que entrega valores uno a uno
itertools Herramientas para componer iteradores
Fecha ingenua Fecha/hora sin zona horaria asociada
Variable de entorno Configuración proporcionada por el sistema

Prerrequisitos

1.6 · Listas y tuplas, 1.9 · Cadenas, 1.10 · Archivos y 1.13 · POO.

1. Elegir un módulo antes de escribir código

Relaciona primero la tarea con una herramienta:

Necesidad Módulo o tipo Uso habitual
Cálculos matemáticos math sqrt, ceil, isclose
Elección reproducible random.Random simulaciones y pruebas
Fechas datetime date, timedelta, datetime
Rutas pathlib.Path ya tratado en 1.10
CSV csv DictReader, DictWriter
Frecuencias collections.Counter contar y obtener los más frecuentes
Agrupar o encadenar itertools procesar sin materializar todo
CLI argparse argumentos de terminal
Sistema operativo os entorno y operaciones heredadas

La tabla sirve como guía para buscar una API concreta y leer su documentación antes de usarla; no es necesario importar todos los módulos.

2. Matemáticas y aleatoriedad controlada

math aporta funciones que no forman parte de los operadores básicos:

import math

print(math.sqrt(81))
print(math.ceil(2.1), math.floor(2.9))
print(math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-9))

Salida esperada:

9.0
3 2
True

La comparación 0.1 + 0.2 == 0.3 puede fallar por la representación binaria de los decimales. math.isclose expresa mejor la intención cuando comparas resultados numéricos aproximados.

Para pruebas o simulaciones crea un generador local con una semilla:

import random

azar = random.Random(42)
print(azar.randint(1, 6))
print(azar.sample(["A", "B", "C", "D"], k=2))

# La misma semilla, otro generador: mismos numeros.
otro_azar = random.Random(42)
print(otro_azar.randint(1, 6))
print(otro_azar.sample(["A", "B", "C", "D"], k=2))

Salida esperada:

6
['A', 'D']
6
['A', 'D']

Las dos últimas líneas repiten exactamente las dos primeras. Eso es lo que compra la semilla: dos ejecuciones del mismo programa dan el mismo resultado, y por eso un test que dependa de números aleatorios puede comprobarse. Fíjate también en que azar y otro_azar son objetos distintos: cada random.Random(semilla) tiene su propio estado, así que crear uno no interrumpe al otro. Usar el generador global (random.randint(...) sin más) sí dependería del orden en que se ejecutara el resto del programa.

Lo que no te da la semilla es compatibilidad entre versiones de Python: el algoritmo de random.sample puede cambiar, así que la lista concreta ['A', 'D'] es la salida de esta versión. Si tu programa guarda ese resultado en un fichero o lo compara en un test, no dependas del orden; extrae, ordena o comprueba la longitud.

Una semilla no hace seguro un sistema aleatorio

random sirve para simulaciones y pruebas, nunca para contraseñas, tokens ni identificadores de sesión: su algoritmo es público y se puede predecir. Para eso está el módulo secrets, que usa el generador del sistema operativo.

3. Fechas y duraciones

datetime representa fechas y permite operar con duraciones:

from datetime import date, timedelta

inicio = date(2026, 8, 21)
revision = inicio + timedelta(days=10)
print(inicio.isoformat())
print(revision.isoformat())
print((revision - inicio).days)

Salida esperada:

2026-08-21
2026-08-31
10

Para una fecha sin hora, date es suficiente. Cuando trabajes con horas reales, zonas horarias o APIs, usa datetime consciente de zona y documenta qué zona representa. No mezcles fechas como cadenas si necesitas ordenar o calcular.

Fechas y zonas horarias

datetime.now() sin zona describe la hora local del proceso, no una hora universal. Para sistemas distribuidos conviene acordar UTC o una zona explícita y convertir solo al mostrarla.

4. Leer CSV con estructura

Si un fichero tabular es sencillo, csv.DictReader evita separar cadenas a mano:

import csv
from io import StringIO

contenido = "producto,importe\nlibro,12.5\nteclado,45.0\nlibro,7.5\n"
filas = csv.DictReader(StringIO(contenido))
ventas = [{**fila, "importe": float(fila["importe"])} for fila in filas]

for venta in ventas:
    print(venta["producto"], venta["importe"])
print("Total:", sum(venta["importe"] for venta in ventas))

Salida esperada:

libro 12.5
teclado 45.0
libro 7.5
Total: 65.0

Dos construcciones de este bloque merecen una pausa, porque son distintas de lo que ya conoces:

  • StringIO es un fichero que vive en memoria. contenido es una cadena, y StringIO(contenido) la envuelve en algo que se comporta como un fichero abierto. Eso permite probar un lector de CSV sin escribir nada en disco, y por eso aparece en los tests. Para trabajar con un fichero de verdad, sustitúyelo por la ruta real.
  • {**fila, "importe": ...} fusiona dos diccionarios. El ** delante del nombre no es aquí «pasar argumentos» (como en **kwargs), sino «desempaqueta este diccionario dentro del nuevo». El resultado copia las claves de fila y añade importe ya convertido a número, sustituyendo el texto que traía. El orden importa: la clave "importe" va después de **fila, así que gana la nueva. Si la pones antes, se perdería la conversión.

DictReader usa la primera fila como nombres de campo y entrega cada fila como un diccionario. Los valores llegan como texto, por lo que la conversión a float es una decisión explícita. Para un fichero real sustituirías StringIO por Path(...).open(newline="", encoding="utf-8"); newline="" deja que el módulo csv gestione los saltos de línea, como recomienda su documentación.

5. collections: contar y agrupar

Counter expresa directamente un problema de frecuencias:

from collections import Counter, defaultdict

productos = ["libro", "teclado", "libro", "ratón", "libro"]
frecuencias = Counter(productos)
print(frecuencias.most_common(2))

por_categoria = defaultdict(list)
por_categoria["entrada"].append("teclado")
por_categoria["entrada"].append("ratón")
print(dict(por_categoria))

Salida esperada:

[('libro', 3), ('teclado', 1)]
{'entrada': ['teclado', 'ratón']}

defaultdict(list) crea la lista al acceder a una clave nueva. Úsalo cuando esa creación automática sea parte de la intención; si una clave ausente es un error, dict normal y get comunican mejor el contrato.

6. Ampliación · itertools: procesar sin crear colecciones innecesarias

Este apartado y el siguiente son ampliación: estúdialos después de terminar el núcleo del capítulo (apartados 1–5 y 8) y el producto evaluable.

Los iteradores permiten encadenar datos y consumir solo la parte necesaria:

from itertools import chain, islice

fuentes = [["a", "b"], ["c"], ["d", "e"]]
primera_parte = list(islice(chain.from_iterable(fuentes), 4))
print(primera_parte)

Salida esperada:

['a', 'b', 'c', 'd']

chain.from_iterable recorre varias colecciones como una sola y islice toma los primeros elementos sin construir antes la secuencia completa. Es útil en pipelines de datos grandes, aunque una lista sencilla puede ser más legible en un caso pequeño.

Ampliación: deque y pairwise

collections.deque es adecuado para una cola con entradas y salidas por ambos extremos. itertools.pairwise recorre parejas consecutivas. No los introduzcas solo para sustituir una lista: elige la estructura cuando el patrón de acceso lo justifique.

7. Ampliación · functools y funciones reutilizables

En 1.8 viste decoradores y lru_cache. Otra utilidad breve es partial, que fija parte de los argumentos de una función:

from functools import partial

redondear_dos = partial(round, ndigits=2)
print(redondear_dos(3.14159))

Salida esperada:

3.14

Si el nombre redondear_dos es más claro que la llamada parametrizada, partial puede mejorar la composición. Si la función resultante necesita una explicación larga, define una función con def y un docstring.

8. Variables de entorno y sistema

os.environ permite leer configuración proporcionada por el entorno:

import os

modo = os.environ.get("ASISTENTE_MODO", "local")
print(modo)

Salida esperada:

local

La salida mostrada corresponde a un equipo donde ASISTENTE_MODO no está definida; si estuviera definida, os.environ.get leería ese valor. No guardes claves secretas en el código ni en el repositorio. En 1.15 se usará python-dotenv como comodidad local; el principio es el mismo: la configuración entra desde fuera del código.

8.1 Ampliación: argparse y subprocess

Para una CLI, argparse valida y documenta argumentos sin analizar sys.argv a mano:

Este bloque va en un fichero cli.py, no en una celda del cuaderno. La razón es concreta: parse_args() lee los argumentos con los que se arrancó el programa desde la terminal, y si no entiende alguno termina el programa entero con un código de error. En un cuaderno o en el REPL no hay argumentos de terminal que leer, y ese final brusco interrumpiría la sesión.

# cli.py
import argparse

parser = argparse.ArgumentParser(description="Resume notas")
parser.add_argument("--limite", type=int, default=5)
args = parser.parse_args()
print(f"Límite: {args.limite}")

Comprobación desde la terminal:

uv run python cli.py --limite 3

Salida esperada:

Límite: 3

Si lo llamas sin --limite, se usa el valor por defecto que declaraste:

uv run python cli.py

Salida esperada:

Límite: 5

Y si le das algo que no es un entero, argparse avisa y termina en lugar de continuar con un valor raro:

uv run python cli.py --limite abc

Salida esperada:

usage: cli.py [-h] [--limite LIMITE]
cli.py: error: argument --limite: invalid int value: 'abc'

Ahí está el valor de type=int: el error sale con un mensaje que señala la opción culpable, no un ValueError de Python en una línea sin contexto. Esa es la diferencia entre validar los argumentos al arrancar o descubrirlos a mitad del programa.

subprocess puede ejecutar programas externos, pero una entrada de usuario no debe convertirse sin control en un comando. Evita shell=True salvo que comprendas y controles todas las implicaciones; esta ampliación no es necesaria para el proyecto de la unidad.

Aplicación práctica: informe reproducible de ventas

Integraremos varias herramientas sin instalar nada:

import csv
from collections import Counter
from datetime import date
from io import StringIO

contenido = (
    "producto,categoria,importe\n"
    "libro,lectura,12.5\n"
    "teclado,entrada,45.0\n"
    "libro,lectura,7.5\n"
    "monitor,oficina,180.0\n"
)
filas = csv.DictReader(StringIO(contenido))
ventas = [{**fila, "importe": float(fila["importe"])} for fila in filas]

productos = Counter(venta["producto"] for venta in ventas)
total = sum(venta["importe"] for venta in ventas)
print("Fecha:", date(2026, 8, 21).isoformat())
print("Número de ventas:", len(ventas))
print("Total:", total)
print("Más vendido:", productos.most_common(1)[0])

Salida esperada:

Fecha: 2026-08-21
Número de ventas: 4
Total: 245.0
Más vendido: ('libro', 2)

La salida es reproducible porque los datos, la fecha y el número de filas están declarados. En una aplicación real documentarías el origen, el formato y la zona horaria de la fecha.

Fíjate en que la fecha se escribe con date(2026, 8, 21) y no con date.today(): el informe da el mismo resultado dentro de un año. Si quisieras la fecha real, date.today() la daría, pero entonces la salida cambiaría cada día y no podrías compararla con la de nadie.

Errores frecuentes

  • Instalar una dependencia externa antes de buscar una solución estándar.
  • Tratar todos los valores leídos de CSV como números sin convertirlos.
  • Usar random para secretos o tokens.
  • Comparar fechas como texto cuando el formato no es uniforme.
  • Consumir un iterador y después intentar recorrerlo otra vez.
  • Usar defaultdict y crear claves por accidente al consultarlas.
  • Introducir subprocess o shell=True con texto de usuario sin validación.
  • Repetir en este capítulo todo el contenido de pathlib o regex de los anteriores: aquí interesa integrarlos, no memorizarlos de nuevo.

Práctica de transferencia

  1. Lee un CSV con DictReader y calcula el importe total y el producto más frecuente.
  2. Crea un informe de fechas con una fecha inicial y tres vencimientos usando timedelta.
  3. Procesa dos listas de eventos con chain y conserva solo los cinco primeros con islice.
  4. Usa Counter para detectar valores repetidos y explica qué información se pierde al convertir directamente a set.
  5. Añade un argumento --limite a una CLI pequeña y comprueba la validación de un valor no entero.

Qué debes comprobar: en el punto 1, que el total coincide con una suma hecha a mano; en el 4, qué valores repetidos detecta Counter y que el set solo te dice cuáles son distintos, no cuántas veces aparece cada uno.

Producto evaluable

Crea 10_biblioteca_estandar.py como script o cuaderno marimo. Debe:

  1. Leer un conjunto de ventas en CSV reproducible.
  2. Convertir tipos de forma explícita y calcular total, fecha de informe y frecuencias.
  3. Usar al menos datetime, csv y Counter (los iteradores de itertools quedan para la ampliación).
  4. Mostrar una salida estable y explicar por qué cada módulo es adecuado.
  5. Separar el núcleo obligatorio de una ampliación opcional (argparse, deque o una ruta de fichero real).

Criterio de aceptación: el informe se ejecuta sin paquetes externos, produce la misma salida con los mismos datos y no depende de una ordenación accidental ni de la hora actual del equipo.

Formato de entrega: script o cuaderno marimo ejecutado, con salida esperada, interpretación y conclusión.

Resumen y referencia rápida

Necesitas API
Matemáticas math.sqrt, math.isclose
Aleatoriedad reproducible random.Random(semilla)
Fechas y duración date, datetime, timedelta
CSV tabular csv.DictReader / DictWriter
Frecuencias collections.Counter
Agrupación collections.defaultdict
Iteradores itertools.chain, islice
Funciones parciales functools.partial
Configuración os.environ.get
CLI argparse.ArgumentParser

Siguiente: 1.15 · Librerías externas.