Skip to content
This page has been auto-translated and may contain errors.View in English

Módulos y la biblioteca estándar

Python viene con una enorme colección de herramientas listas para usar: aleatoriedad, matemáticas, fechas, rutas de archivos y mucho más. Estas herramientas viven en módulos, y los traes a tu código con import. Ya usaste import json en el capítulo anterior. Este capítulo cubre los imports en profundidad y presenta las partes más útiles de la biblioteca estándar.

La biblioteca estándar de Python ofrece soluciones probadas y documentadas para problemas comunes. Los módulos son la unidad de organización del código: cada archivo es un módulo, cada directorio con un __init__.py es un paquete. El sistema de import localiza los módulos, los compila si es necesario y los almacena en caché en sys.modules para que solo se carguen una vez.

El sistema de imports es una maquinaria por capas: los finders localizan los módulos y los loaders los compilan y ejecutan. Los resultados se almacenan en caché en sys.modules. import foo ejecuta foo.py una vez y vincula el objeto módulo a foo en el namespace actual. from foo import bar solo vincula bar. Entender sys.path, __init__.py y los imports relativos es esencial para construir paquetes.

Importar módulos

El import más simple trae un módulo completo y te permite usar su contenido con notación de punto. También puedes importar nombres específicos de un módulo para usarlos directamente sin el prefijo. Los alias acortan nombres largos.

import module vincula el objeto módulo al nombre module en el ámbito actual. from module import name solo vincula name. Los alias (import module as alias) son comunes con bibliotecas de terceros. Evita from module import *: contamina el namespace y hace difícil saber de dónde vienen los nombres.

import module activa la maquinaria completa de imports, almacena el resultado en caché en sys.modules y vincula el objeto módulo. from module import name es azúcar sintáctico: igualmente importa el módulo completo y luego extrae name. Los imports circulares son una trampa común; la solución suele ser mover los imports dentro de funciones o reestructurar las dependencias del módulo. importlib.import_module() permite imports programáticos.

python
import math

math.sqrt(16)     # 4.0
math.pi           # 3.141592653589793
math.floor(3.9)   # 3
math.ceil(3.1)    # 4

Importa nombres específicos de un módulo para usarlos directamente:

python
from math import sqrt, pi

sqrt(16)    # 4.0 (no se necesita el prefijo "math.")
pi          # 3.141592653589793

Asigna un alias a un módulo o nombre para acortarlo:

python
import math as m

m.sqrt(16)    # 4.0

from math import sqrt as square_root
square_root(25)    # 5.0

Los alias son comunes con bibliotecas populares de terceros (import numpy as np, import pandas as pd). Para los módulos de la biblioteca estándar, prefiere usar el nombre completo; hace el código más fácil de leer.

random

El módulo random genera números aleatorios y hace elecciones aleatorias. Úsalo para juegos, simulaciones, muestreo aleatorio y cualquier otra cosa que necesite imprevisibilidad. Establecer una semilla (seed) hace que los resultados sean reproducibles: la misma semilla produce la misma secuencia cada vez.

random usa un generador pseudoaleatorio Mersenne Twister. La semilla determina la secuencia completa; la misma semilla siempre produce la misma salida. .choice() elige un elemento, .choices() elige con reemplazo, .sample() elige sin reemplazo. .shuffle() modifica la lista in situ y devuelve None.

random usa un PRNG Mersenne Twister (MT19937) con un estado de 624 palabras. random.seed() inicializa el estado; sin él, el estado se siembra desde os.urandom(). Para propósitos criptográficos, usa secrets en su lugar: random no es criptográficamente seguro. random.SystemRandom() envuelve os.urandom() para una alternativa segura con la misma API.

python
import random

random.random()              # float entre 0 y 1 (exclusivo)
random.randint(1, 10)        # entero del 1 al 10 (ambos inclusive)
random.uniform(1.0, 10.0)    # float entre 1.0 y 10.0

colours = ["red", "green", "blue"]
random.choice(colours)       # elige un elemento
random.choices(colours, k=3) # elige k elementos (con reemplazo)
random.sample(colours, k=2)  # elige k elementos (sin reemplazo)

numbers = [1, 2, 3, 4, 5]
random.shuffle(numbers)      # mezcla in situ, devuelve None

Para resultados reproducibles (útil en pruebas y ciencia de datos), establece una semilla antes de generar:

python
random.seed(42)
random.randint(1, 100)   # siempre el mismo valor para la semilla 42

La misma semilla produce la misma secuencia cada vez, en cualquier máquina.

math

El módulo math agrega operaciones matemáticas más avanzadas más allá de los operadores aritméticos básicos. Raíces cuadradas, potencias, logaritmos, trigonometría y valores especiales como pi e infinito están todos aquí.

math ofrece implementaciones a nivel de C de funciones matemáticas estándar. Ten en cuenta que math.pow() siempre devuelve un float, mientras que el operador ** de Python devuelve int para bases y exponentes enteros. math.log(x, base) calcula el logaritmo en cualquier base; math.log(x) calcula el logaritmo natural.

math envuelve las funciones de la biblioteca C <math.h>. Son más rápidas que las implementaciones en Python puro y manejan correctamente los casos extremos (NaN, infinito). math.isnan() y math.isinf() verifican valores especiales IEEE 754. Para números complejos, cmath proporciona las funciones correspondientes. Para matemáticas a nivel de arreglos, numpy es la herramienta estándar.

python
import math

math.sqrt(25)        # 5.0
math.pow(2, 10)      # 1024.0 (igual que 2 ** 10 pero siempre devuelve float)
math.log(100, 10)    # 2.0 (logaritmo base 10)
math.log(math.e)     # 1.0 (logaritmo natural)

math.sin(math.pi / 2)   # 1.0
math.cos(0)             # 1.0

math.ceil(3.2)    # 4
math.floor(3.9)   # 3
math.trunc(3.9)   # 3 (igual que int() para positivos)

math.inf          # infinito
math.isnan(float("nan"))   # True
math.isinf(math.inf)       # True

datetime

El módulo datetime maneja fechas y horas. datetime.now() te da la fecha y hora actuales. strftime() lo formatea como cadena. strptime() parsea una cadena a datetime. timedelta representa una duración que puedes sumar o restar.

datetime, date y timedelta son las clases principales. strftime() formatea un datetime como cadena usando códigos de formato. strptime() parsea una cadena dado un patrón de formato. timedelta admite aritmética: puedes sumar o restar duraciones de fechas y comparar datetimes con <, >, -.

Los objetos datetime son ingenuos por defecto (sin zona horaria). Para datetimes con zona horaria, usa datetime.now(tz=timezone.utc) o datetime.fromisoformat() con un offset. strftime/strptime usan códigos de formato de la biblioteca C; %f da microsegundos. Para mediciones de alta precisión, prefiere time.perf_counter() sobre datetime.now(). El módulo zoneinfo (Python 3.9+) ofrece soporte de zonas horarias IANA.

python
from datetime import datetime, date, timedelta

now   = datetime.now()           # fecha y hora actuales
today = date.today()             # solo fecha actual

print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)

# Formateo
print(now.strftime("%Y-%m-%d"))           # "2024-01-15"
print(now.strftime("%d %B %Y, %H:%M"))   # "15 January 2024, 09:42"

# Parseo
deadline = datetime.strptime("2024-12-31", "%Y-%m-%d")

# Aritmética
tomorrow    = today + timedelta(days=1)
next_week   = today + timedelta(weeks=1)
diff        = deadline - now
print(f"{diff.days} days until deadline")

Códigos comunes de strftime:

CódigoSignificadoEjemplo
%YAño de 4 dígitos2024
%mMes (con cero a la izquierda)01
%dDía (con cero a la izquierda)15
%HHora (24h)09
%MMinuto42
%BNombre completo del mesJanuary

os y pathlib

pathlib es la forma moderna de trabajar con rutas de archivos. Los objetos Path te permiten construir, inspeccionar y navegar rutas usando el operador /. os da acceso a variables de entorno y operaciones de sistema operativo de más bajo nivel. Prefiere pathlib para código nuevo.

pathlib.Path representa rutas del sistema de archivos como objetos con métodos para consulta y navegación. El operador / une componentes de ruta de forma limpia, manejando automáticamente los separadores específicos del SO. os.environ es un objeto tipo diccionario para variables de entorno; os.environ.get("KEY", "default") es seguro para variables faltantes.

pathlib.Path es una base abstracta con PurePosixPath y PureWindowsPath como implementaciones concretas para cada SO. Métodos como .glob(), .rglob() e .iterdir() devuelven generadores. .stat() llama a os.stat() y devuelve un stat_result. Las funciones de os.path aceptan tanto cadenas como objetos Path desde Python 3.6. Prefiere pathlib para código nuevo; usa os.fspath() para convertir Path a str cuando llames a APIs que no aceptan Path.

python
from pathlib import Path

p = Path("data/reports")

p.exists()           # True si la ruta existe
p.is_dir()           # True si es un directorio
p.is_file()          # True si es un archivo

p.mkdir(parents=True, exist_ok=True)   # crea directorios

for f in p.glob("*.csv"):              # todos los archivos CSV del directorio
    print(f.name)                      # solo el nombre del archivo

report = p / "report_jan.csv"          # el operador / une rutas
report.stem       # "report_jan" (nombre sin extensión)
report.suffix     # ".csv"
report.parent     # Path("data/reports")

content = report.read_text()           # lee el contenido del archivo directamente
report.write_text("new content\n")    # escribe directamente

Para el módulo os:

python
import os

os.getcwd()                        # directorio de trabajo actual
os.listdir(".")                    # lista el contenido del directorio
os.path.exists("data.txt")        # True si la ruta existe
os.path.join("data", "file.txt")  # "data/file.txt" (multiplataforma)
os.environ.get("HOME")            # lee una variable de entorno

Prefiere pathlib para código nuevo. Usa os cuando necesites variables de entorno o trabajar con APIs antiguas que esperan cadenas.

timeit

timeit mide cuánto tarda en ejecutarse el código. Es útil cuando quieres comparar dos enfoques y elegir el más rápido. Ejecuta el código muchas veces para obtener una medición estable.

timeit.timeit(stmt, setup, number) cronometra stmt ejecutándolo number veces y devolviendo el tiempo total transcurrido en segundos. La cadena setup se ejecuta una vez antes del bucle cronometrado. Divide el resultado entre number para obtener el tiempo por llamada. Más repeticiones reducen el ruido de la planificación del sistema.

timeit desactiva el recolector de basura durante la medición para reducir el ruido. Usa time.perf_counter() para medición de alta resolución. El parámetro globals pasa un namespace a la sentencia cronometrada. Para microbenchmarks, timeit es la herramienta estándar; para perfilar dónde se gasta el tiempo en un programa más grande, usa cProfile.

python
import timeit

# Cronometrar una sola sentencia
timeit.timeit("sum(range(1000))", number=10000)

# Cronometrar un bloque más complejo
setup = "data = list(range(1000))"
code  = "[x * 2 for x in data]"
time  = timeit.timeit(code, setup=setup, number=10000)
print(f"{time:.4f} seconds for 10,000 runs")

number es la cantidad de veces que se repite. Más repeticiones dan una medición más estable.

string

El módulo string proporciona constantes de cadena predefinidas para letras, dígitos y signos de puntuación. Útil cuando necesitas verificar caracteres o generar cadenas aleatorias a partir de un alfabeto específico.

Las constantes del módulo string (ascii_letters, digits, punctuation) son cadenas simples que puedes indexar, iterar o usar con in. Combinarlas con random.choices() es la forma estándar de generar tokens o contraseñas aleatorias.

Las constantes del módulo string son literales de cadena Python puros sin comportamiento especial. No son conjuntos, así que in es O(n); para pruebas de pertenencia frecuentes, usa set(string.digits). string.Formatter y string.Template son la maquinaria subyacente para str.format() y la sustitución estilo $ respectivamente.

python
import string

string.ascii_lowercase   # "abcdefghijklmnopqrstuvwxyz"
string.ascii_uppercase   # "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
string.ascii_letters     # ambas combinadas
string.digits            # "0123456789"
string.punctuation       # todos los caracteres de puntuación

Útil cuando necesitas verificar caracteres o generar cadenas aleatorias:

python
import string, random

chars    = string.ascii_letters + string.digits
password = "".join(random.choices(chars, k=12))

Crear tus propios módulos

Cualquier archivo de Python es un módulo. Para usarlo desde otro archivo, impórtalo por el nombre del archivo (sin .py). Puedes importar el módulo completo y usar su contenido con notación de punto, o importar nombres específicos directamente.

Cuando Python importa un módulo, ejecuta el archivo de arriba a abajo una vez y almacena el resultado en caché en sys.modules. Los imports posteriores del mismo módulo devuelven el objeto cacheado sin volver a ejecutar el archivo. Para proyectos más grandes, los módulos se organizan en paquetes: directorios con un archivo __init__.py.

La resolución de imports usa sys.path: una lista de directorios que se buscan en orden. sys.path[0] es el directorio del script. La variable de entorno PYTHONPATH antepone directorios adicionales. Los paquetes requieren __init__.py (puede estar vacío) para ser reconocidos. Los imports relativos (from . import module) son válidos dentro de paquetes. importlib.reload() reejecuta un módulo, pero las referencias existentes a objetos antiguos no se actualizan.

python
# utils.py
def clamp(value, lo, hi):
    return max(lo, min(value, hi))

PI = 3.14159
python
# main.py
import utils

utils.clamp(150, 0, 100)   # 100
utils.PI                    # 3.14159

from utils import clamp
clamp(50, 0, 100)           # 50

Python encuentra el módulo buscando en el mismo directorio que el archivo que lo importa (y algunos otros lugares). Para proyectos más grandes, los módulos se organizan en paquetes: directorios con un archivo __init__.py.

__name__ == "__main__"

Cuando Python ejecuta un archivo directamente, __name__ se establece en "__main__". Cuando el mismo archivo se importa como módulo, __name__ es el nombre del módulo. Este patrón te permite escribir código que se ejecuta cuando ejecutas el archivo directamente pero se omite cuando otro módulo importa el archivo.

if __name__ == "__main__": es el guardián estándar para código ejecutable del módulo. Permite que un módulo sea tanto importable (exponiendo sus funciones) como ejecutable directamente (con código de prueba o demostración). Sin él, importar el módulo ejecutaría cualquier código de nivel superior, lo cual casi nunca es deseado.

__name__ lo establece la maquinaria de imports: "__main__" para el script de punto de entrada, el nombre puntuado del módulo en caso contrario. El guardián previene que los efectos secundarios (código de arranque, parseo de argumentos, ejecución de pruebas) se ejecuten al importar. Para herramientas de línea de comandos, poner la lógica del punto de entrada en una función main() y llamarla bajo el guardián es el patrón idiomático.

python
# utils.py
def clamp(value, lo, hi):
    return max(lo, min(value, hi))

if __name__ == "__main__":
    # esto solo se ejecuta cuando haces: python utils.py
    # no cuando haces: import utils
    print(clamp(150, 0, 100))   # 100

Este es un patrón estándar para cualquier módulo que también sea útil como script independiente.

Lo más destacado de la biblioteca estándar

Algunos módulos más que vale la pena conocer. Cada uno resuelve un problema común que llevaría un trabajo significativo implementar tú mismo.

La biblioteca estándar es extensa; lo destacado a continuación es lo que encontrarás más a menudo en código de producción. Para una referencia completa, docs.python.org/3/library es la fuente autoritativa.

La biblioteca estándar es un conjunto curado de módulos bien probados y documentados. Antes de recurrir a un paquete de terceros, verifica si la biblioteca estándar tiene una solución: functools, itertools, contextlib, dataclasses, typing y abc ofrecen herramientas que los paquetes de terceros a menudo reinventan.

collections: tipos de contenedores especializados:

python
from collections import Counter, defaultdict, deque

Counter(["a", "b", "a", "c", "a"])   # Counter({'a': 3, 'b': 1, 'c': 1})
defaultdict(list)                      # dict que crea claves faltantes automáticamente
deque([1, 2, 3], maxlen=5)            # append/pop rápido por ambos extremos

itertools: herramientas para trabajar con iterables:

python
import itertools

list(itertools.chain([1, 2], [3, 4]))          # [1, 2, 3, 4]
list(itertools.islice(range(100), 5))          # [0, 1, 2, 3, 4]
list(itertools.combinations([1, 2, 3], 2))     # [(1, 2), (1, 3), (2, 3)]
list(itertools.product([0, 1], repeat=2))      # [(0,0), (0,1), (1,0), (1,1)]

sys: acceso al intérprete de Python:

python
import sys

sys.argv        # lista de argumentos de línea de comandos
sys.exit(1)     # sale con un código de estado
sys.version     # cadena con la versión de Python

Paquetes de terceros: más allá de la biblioteca estándar, pip instala paquetes de la comunidad:

bash
pip install requests    # biblioteca HTTP
pip install pandas      # manipulación de datos
pip install numpy       # computación numérica

Los paquetes de terceros están fuera del alcance de esta guía, pero el patrón siempre es el mismo: pip install, luego import.

En la práctica

Combinando random, string y datetime para generar IDs únicos de partida con marcas de tiempo:

python
import random
import string
from datetime import datetime

def generate_game_id(length: int = 8) -> str:
    chars = string.ascii_uppercase + string.digits
    return "".join(random.choices(chars, k=length))

def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

game_id = generate_game_id()
print(f"[{timestamp()}] Starting game {game_id}")

scores = [random.randint(50, 100) for _ in range(5)]
print(f"Round scores: {scores}")
print(f"Best: {max(scores)}")

Usando pathlib y datetime para encontrar archivos en un directorio e informar sus tamaños:

python
from pathlib import Path
from datetime import datetime

def find_files(directory: str, pattern: str = "*.csv") -> list[Path]:
    return sorted(Path(directory).glob(pattern))

def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

files = find_files(".", "*.md")[:3]
print(f"[{timestamp()}] Found {len(files)} file(s)")
for f in files:
    size = f.stat().st_size if f.exists() else 0
    print(f"  {f.name} ({size} bytes)")

Leyendo la configuración de una app desde variables de entorno con valores predeterminados tipados, y escribiendo entradas estructuradas de registro de accesos como JSON delimitado por saltos de línea:

python
import os
import json
from datetime import datetime
from pathlib import Path

def load_env_config() -> dict:
    return {
        "debug":     os.environ.get("DEBUG", "false").lower() == "true",
        "port":      int(os.environ.get("PORT", "8080")),
        "log_level": os.environ.get("LOG_LEVEL", "INFO"),
    }

def write_access_log(method: str, path: str, status: int) -> None:
    log_dir = Path("logs")
    log_dir.mkdir(exist_ok=True)
    entry = {
        "ts":     datetime.now().isoformat(),
        "method": method,
        "path":   path,
        "status": status,
    }
    with open(log_dir / "access.jsonl", "a") as f:
        f.write(json.dumps(entry) + "\n")

config = load_env_config()
print(f"Starting on port {config['port']}, debug={config['debug']}")
write_access_log("GET", "/users", 200)

JSON delimitado por saltos de línea (.jsonl) es un formato de registro común: cada línea es un objeto JSON válido, lo que facilita transmitir, agregar y parsear línea por línea sin cargar el archivo completo.