Python & AI Tutorials Logo
Programación Python

39. Módulos esenciales de la biblioteca estándar

La biblioteca estándar de Python es una colección de módulos que vienen integrados con Python: no necesitas instalar nada extra para usarlos. Estos módulos proporcionan herramientas potentes para tareas comunes de programación: generar números aleatorios, trabajar con fechas y horas, intercambiar datos con otros programas y usar estructuras de datos especializadas que van más allá de las listas y diccionarios básicos.

En este capítulo, exploraremos cinco módulos esenciales de la biblioteca estándar que usarás con frecuencia en la programación Python del mundo real.

39.1) Generar aleatoriedad con random

El módulo random proporciona funciones para generar números aleatorios y hacer selecciones aleatorias. Esto es útil para simulaciones, juegos, pruebas, muestreo de datos y cualquier situación en la que necesites un comportamiento impredecible.

39.1.1) Generar enteros aleatorios con randint()

La función randint() genera un entero aleatorio entre dos valores, incluyendo ambos extremos:

python
import random
 
# Simular tirar un dado de seis caras
die_roll = random.randint(1, 6)
print(f"You rolled: {die_roll}")  # Output: You rolled: 4 (varies each run)
 
# Generar una edad aleatoria entre 18 y 65
age = random.randint(18, 65)
print(f"Random age: {age}")  # Output: Random age: 42 (varies)

Fíjate en que tanto el valor inicial como el final se incluyen en los resultados posibles. randint(1, 6) puede devolver 1, 2, 3, 4, 5 o 6: los seis valores son posibles.

Aquí tienes un ejemplo práctico que simula varias tiradas de dados:

python
import random
 
# Simular tirar dos dados y calcular su suma
die1 = random.randint(1, 6)
die2 = random.randint(1, 6)
total = die1 + die2
 
print(f"Die 1: {die1}")  # Output: Die 1: 3 (varies)
print(f"Die 2: {die2}")  # Output: Die 2: 5 (varies)
print(f"Total: {total}")  # Output: Total: 8 (varies)
 
if total == 7:
    print("Lucky seven!")
elif total == 2 or total == 12:
    print("Snake eyes or boxcars!")

Por qué ambos extremos son inclusivos: Esto hace que randint() sea intuitivo para casos de uso comunes. Cuando quieres un número del 1 al 6 (como un dado), escribes randint(1, 6) y tanto 1 como 6 son resultados posibles.

39.1.2) Generar números aleatorios de punto flotante

Para números decimales aleatorios, usa random() (devuelve un float entre 0.0 y 1.0) o uniform() (devuelve un float entre dos valores especificados):

python
import random
 
# Generar un float aleatorio entre 0.0 y 1.0 (0.0 incluido, 1.0 excluido)
probability = random.random()
print(f"Random probability: {probability:.4f}")  # Output: Random probability: 0.7284 (varies)
 
# Generar una temperatura aleatoria entre 15.0 y 30.0 grados
temperature = random.uniform(15.0, 30.0)
print(f"Temperature: {temperature:.2f}°C")  # Output: Temperature: 23.47°C (varies)
 
# Generar un precio aleatorio entre $10.00 y $99.99
price = random.uniform(10.0, 99.99)
print(f"Price: ${price:.2f}")  # Output: Price: $45.67 (varies)

La función random() es útil cuando necesitas un valor de probabilidad o un porcentaje. La función uniform() es mejor cuando necesitas un decimal aleatorio dentro de un rango específico.

39.1.3) Hacer elecciones aleatorias con choice()

La función choice() selecciona aleatoriamente un elemento de una secuencia (lista(list), tupla(tuple) o cadena(string)):

python
import random
 
# Seleccionar un color al azar
colors = ["red", "blue", "green", "yellow", "purple"]
selected_color = random.choice(colors)
print(f"Selected color: {selected_color}")  # Output: Selected color: green (varies)
 
# Seleccionar un ganador al azar entre participantes
participants = ["Alice", "Bob", "Charlie", "Diana"]
winner = random.choice(participants)
print(f"The winner is: {winner}")  # Output: The winner is: Bob (varies)
 
# Seleccionar un carácter al azar de una cadena
vowels = "aeiou"
random_vowel = random.choice(vowels)
print(f"Random vowel: {random_vowel}")  # Output: Random vowel: i (varies)

Esto es particularmente útil para juegos, muestreo aleatorio o selección de datos de prueba aleatorios. Cada elemento en la secuencia tiene la misma probabilidad de ser elegido.

Aquí tienes un ejemplo más complejo que simula un juego de preguntas sencillo:

python
import random
 
# Preguntas del cuestionario con sus respuestas
questions = [
    ("What is 2 + 2?", "4"),
    ("What is the capital of France?", "Paris"),
    ("What color is the sky?", "blue")
]
 
# Seleccionar una pregunta al azar
question, correct_answer = random.choice(questions)
print(f"Question: {question}")
 
user_answer = input("Your answer: ")
if user_answer.lower() == correct_answer.lower():
    print("Correct!")
else:
    print(f"Wrong! The answer was: {correct_answer}")

39.1.4) Seleccionar varios elementos aleatorios con sample()

Cuando necesitas seleccionar varios elementos únicos de una secuencia, usa sample(). Es como sacar cartas de una baraja sin reemplazo: una vez se selecciona un elemento, no se seleccionará de nuevo:

python
import random
 
# Seleccionar 3 estudiantes al azar para un proyecto en grupo
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
group = random.sample(students, 3)
print(f"Group members: {group}")  # Output: Group members: ['Diana', 'Alice', 'Frank'] (varies)
 
# Sacar 5 números de lotería del 1 al 50 (sin duplicados)
lottery_numbers = random.sample(range(1, 51), 5)
lottery_numbers.sort()  # Ordenar para mostrar
print(f"Lottery numbers: {lottery_numbers}")  # Output: Lottery numbers: [7, 15, 23, 38, 49] (varies)

El segundo argumento de sample() especifica cuántos elementos seleccionar. El número debe ser menor o igual que la longitud de la secuencia: no puedes seleccionar más elementos de los que hay disponibles.

39.1.5) Barajar secuencias con shuffle()

La función shuffle() reordena aleatoriamente los elementos de una lista(list) in place (modificando la lista original):

python
import random
 
# Barajar una baraja de cartas
cards = ["A♠", "K♠", "Q♠", "J♠", "10♠", "9♠", "8♠", "7♠"]
print(f"Original: {cards}")
random.shuffle(cards)
print(f"Shuffled: {cards}")  # Output: Shuffled: ['Q♠', '7♠', 'A♠', '10♠', '9♠', 'J♠', 'K♠', '8♠'] (varies)
 
# Barajar preguntas de un cuestionario para un orden aleatorio
questions = ["Question 1", "Question 2", "Question 3", "Question 4"]
random.shuffle(questions)
print(f"Randomized order: {questions}")  # Output: Randomized order: ['Question 3', 'Question 1', 'Question 4', 'Question 2'] (varies)

Funciones del módulo Random

randint: Enteros aleatorios

random/uniform: Floats aleatorios

choice: Elegir un elemento

sample: Elegir varios elementos únicos

shuffle: Reordenar lista in place

Inclusivo en ambos extremos

random: 0.0 a 1.0

uniform: Rango personalizado

Probabilidad igual para cada uno

Sin duplicados

Modifica la lista original

39.2) Trabajar con fechas y horas

El módulo datetime proporciona clases para trabajar con fechas, horas e intervalos de tiempo. Esto es esencial para planificación, logging, cálculo de duraciones y cualquier aplicación que necesite registrar cuándo ocurren las cosas.

39.2.1) Obtener la fecha y hora actuales

La clase datetime representa un punto específico en el tiempo con componentes de fecha y hora:

python
from datetime import datetime
 
# Obtener la fecha y hora actuales
now = datetime.now()
print(f"Current datetime: {now}")
# Output: Current datetime: 2026-01-02 14:30:45.123456
 
# Acceder a componentes individuales
print(f"Year: {now.year}")      # Output: Year: 2026
print(f"Month: {now.month}")    # Output: Month: 1
print(f"Day: {now.day}")        # Output: Day: 2
print(f"Hour: {now.hour}")      # Output: Hour: 14
print(f"Minute: {now.minute}")  # Output: Minute: 30
print(f"Second: {now.second}")  # Output: Second: 45

Para solo la fecha (sin hora), usa la clase date:

python
from datetime import date
 
# Obtener la fecha de hoy
today = date.today()
print(f"Today: {today}")  # Output: Today: 2026-01-02
 
print(f"Year: {today.year}")    # Output: Year: 2026
print(f"Month: {today.month}")  # Output: Month: 1
print(f"Day: {today.day}")      # Output: Day: 2

39.2.2) Crear fechas y horas específicas

Puedes crear objetos datetime y date para puntos específicos en el tiempo:

python
from datetime import datetime, date
 
# Crear una fecha específica
birthday = date(1995, 7, 15)
print(f"Birthday: {birthday}")  # Output: Birthday: 1995-07-15
 
# Crear un datetime específico
meeting = datetime(2026, 3, 15, 14, 30)  # March 15, 2026 at 2:30 PM
print(f"Meeting: {meeting}")  # Output: Meeting: 2026-03-15 14:30:00

Esto es útil para representar fechas límite, citas, fechas históricas o cualquier punto fijo en el tiempo:

python
from datetime import date
 
# Fechas importantes en un proyecto
project_start = date(2026, 1, 15)
project_end = date(2026, 6, 30)
 
print(f"Project duration: {project_start} to {project_end}")
# Output: Project duration: 2026-01-15 to 2026-06-30

39.2.3) Calcular diferencias de tiempo con timedelta

La clase timedelta representa una duración: la diferencia entre dos fechas u horas. Puedes usarla para calcular cuánto tiempo ha pasado o para sumar/restar tiempo a las fechas:

python
from datetime import date, timedelta
 
# Calcular la edad
birth_date = date(1995, 7, 15)
today = date(2026, 1, 2)
age_delta = today - birth_date
 
print(f"Days since birth: {age_delta.days}")  # Output: Days since birth: 11128
print(f"Years (approximate): {age_delta.days // 365}")  # Output: Years (approximate): 30

Cuando restas una fecha de otra, obtienes un objeto timedelta. El atributo days te dice la cantidad de días en esa duración.

También puedes crear objetos timedelta directamente para representar duraciones específicas:

python
from datetime import date, timedelta
 
# Sumar días a una fecha
today = date(2026, 1, 2)
one_week = timedelta(days=7)
next_week = today + one_week
 
print(f"Today: {today}")        # Output: Today: 2026-01-02
print(f"Next week: {next_week}")  # Output: Next week: 2026-01-09
 
# Restar días a una fecha
thirty_days_ago = today - timedelta(days=30)
print(f"30 days ago: {thirty_days_ago}")  # Output: 30 days ago: 2025-12-03

timedelta puede representar días, segundos, microsegundos, milisegundos, minutos, horas y semanas:

python
from datetime import datetime, timedelta
 
# Calcular una fecha límite
now = datetime(2026, 1, 2, 14, 30)
deadline = now + timedelta(hours=48, minutes=30)
 
print(f"Current time: {now}")    # Output: Current time: 2026-01-02 14:30:00
print(f"Deadline: {deadline}")   # Output: Deadline: 2026-01-04 15:00:00
 
# Calcular el tiempo restante
time_left = deadline - now
print(f"Hours remaining: {time_left.total_seconds() / 3600}")  # Output: Hours remaining: 48.5

El método total_seconds() convierte toda la duración a segundos, que luego puedes convertir a horas, minutos o cualquier otra unidad.

Aquí tienes un ejemplo práctico para calcular hitos de un proyecto:

python
from datetime import date, timedelta
 
# Planificación del proyecto
project_start = date(2026, 1, 15)
sprint_duration = timedelta(weeks=2)
 
sprint_1_end = project_start + sprint_duration
sprint_2_end = sprint_1_end + sprint_duration
sprint_3_end = sprint_2_end + sprint_duration
 
print(f"Sprint 1: {project_start} to {sprint_1_end}")
# Output: Sprint 1: 2026-01-15 to 2026-01-29
print(f"Sprint 2: {sprint_1_end} to {sprint_2_end}")
# Output: Sprint 2: 2026-01-29 to 2026-02-12
print(f"Sprint 3: {sprint_2_end} to {sprint_3_end}")
# Output: Sprint 3: 2026-02-12 to 2026-02-26

39.2.4) Comparar fechas y horas

Los objetos date y datetime se pueden comparar usando operadores estándar de comparación:

python
from datetime import date
 
# Comparar fechas
date1 = date(2026, 1, 15)
date2 = date(2026, 2, 20)
date3 = date(2026, 1, 15)
 
print(date1 < date2)   # Output: True
print(date1 == date3)  # Output: True
print(date2 > date1)   # Output: True

Esto es útil para comprobar fechas límite, validar rangos de fechas y ordenar fechas:

python
from datetime import date
 
# Comprobar si una fecha está en el pasado
event_date = date(2025, 12, 25)
today = date(2026, 1, 2)
 
if event_date < today:
    print("This event has already passed")  # Output: This event has already passed
else:
    print("This event is upcoming")
 
# Ordenar una lista de fechas
important_dates = [
    date(2026, 3, 15),
    date(2026, 1, 10),
    date(2026, 2, 28)
]
 
important_dates.sort()
print("Dates in order:")  # Output: Dates in order:
for d in important_dates:
    print(f"  {d}")
# Output:
#   2026-01-10
#   2026-02-28
#   2026-03-15

39.2.5) Dar formato a fechas y horas con strftime()

El método strftime() (string format time) convierte fechas y horas en cadenas con formato. Especificas el formato usando códigos especiales:

python
from datetime import datetime
 
now = datetime(2026, 1, 2, 14, 30, 45)
 
# Formatos de fecha comunes
print(now.strftime("%Y-%m-%d"))           # Output: 2026-01-02
print(now.strftime("%m/%d/%Y"))           # Output: 01/02/2026
print(now.strftime("%B %d, %Y"))          # Output: January 02, 2026
print(now.strftime("%A, %B %d, %Y"))      # Output: Friday, January 02, 2026
 
# Formatos de hora comunes
print(now.strftime("%H:%M:%S"))           # Output: 14:30:45
print(now.strftime("%I:%M %p"))           # Output: 02:30 PM
 
# Formatos combinados
print(now.strftime("%Y-%m-%d %H:%M:%S"))  # Output: 2026-01-02 14:30:45
print(now.strftime("%B %d, %Y at %I:%M %p"))  # Output: January 02, 2026 at 02:30 PM

Códigos de formato comunes:

CodeDescriptionExample
%YAño con siglo2026
%mMes como número con cero a la izquierda (01-12)01
%dDía como número con cero a la izquierda (01-31)02
%BNombre completo del mesJanuary
%bNombre corto del mesJan
%ANombre completo del día de la semanaFriday
%aNombre corto del día de la semanaFri
%HHora en formato 24 horas (00-23)14
%IHora en formato 12 horas (01-12)02
%MMinuto (00-59)30
%SSegundo (00-59)45
%pAM/PMPM

Aquí tienes un ejemplo práctico creando una entrada de log:

python
from datetime import datetime
 
def log_event(message):
    """Registrar un evento con marca de tiempo"""
    now = datetime.now()
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
    print(f"[{timestamp}] {message}")
 
log_event("User logged in")
# Output: [2026-01-02 14:30:45] User logged in
 
log_event("File uploaded successfully")
# Output: [2026-01-02 14:30:45] File uploaded successfully

39.2.6) Analizar fechas desde cadenas con strptime()

La función strptime() (string parse time) convierte cadenas con formato de vuelta a objetos datetime. Especificas los mismos códigos de formato para decirle a Python cómo interpretar la cadena:

python
from datetime import datetime
 
# Analizar distintos formatos de fecha
date_str1 = "2026-01-15"
date1 = datetime.strptime(date_str1, "%Y-%m-%d")
print(f"Parsed: {date1}")  # Output: Parsed: 2026-01-15 00:00:00
 
date_str2 = "January 15, 2026"
date2 = datetime.strptime(date_str2, "%B %d, %Y")
print(f"Parsed: {date2}")  # Output: Parsed: 2026-01-15 00:00:00
 
# Analizar datetime con hora
datetime_str = "2026-01-15 14:30:00"
dt = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
print(f"Parsed: {dt}")  # Output: Parsed: 2026-01-15 14:30:00

Esto es esencial al leer fechas desde archivos, entrada del usuario o fuentes de datos externas:

python
from datetime import datetime
 
# Analizar entrada del usuario
user_input = "03/15/2026"
try:
    event_date = datetime.strptime(user_input, "%m/%d/%Y")
    print(f"Event scheduled for: {event_date.strftime('%B %d, %Y')}")
    # Output: Event scheduled for: March 15, 2026
except ValueError:
    print("Invalid date format. Please use MM/DD/YYYY")

Importante: La cadena de formato debe coincidir exactamente con la cadena de entrada, o obtendrás un ValueError:

python
from datetime import datetime
 
# Esto fallará: el formato no coincide
try:
    datetime.strptime("2026-01-15", "%m/%d/%Y")  # Wrong format
except ValueError as e:
    print(f"Error: {e}")
    # Output: Error: time data '2026-01-15' does not match format '%m/%d/%Y'

Módulo datetime

datetime.now: Fecha/hora actual

date.today: Fecha actual

datetime/date: Crear fechas específicas

timedelta: Duraciones de tiempo

strftime: Dar formato a cadena

strptime: Analizar desde cadena

Sumar/restar a fechas

Calcular diferencias

%Y, %m, %d, %H, %M, %S

Debe coincidir exactamente con el formato

39.3) Leer y escribir datos JSON

JSON (JavaScript Object Notation) es un formato de texto para almacenar e intercambiar datos estructurados. Es el formato más común para APIs web, archivos de configuración e intercambio de datos entre programas. El módulo json de Python facilita convertir entre estructuras de datos de Python y texto JSON.

39.3.1) Comprender la estructura de JSON

JSON se parece a los diccionarios(dict) y listas(list) de Python, pero con algunas diferencias:

JSON admite estos tipos de datos:

  • Objetos (como diccionarios(dict) de Python): {"name": "Alice", "age": 30}
  • Arrays (como listas(list) de Python): [1, 2, 3, 4]
  • Cadenas: "hello" (debe usar comillas dobles)
  • Números: 42, 3.14
  • Booleanos: true, false (en minúsculas)
  • Null: null (como None de Python)

Diferencias clave con Python:

  • JSON usa true/false/null en lugar de True/False/None de Python
  • Las cadenas JSON deben usar comillas dobles ("text"), no comillas simples
  • JSON no admite tuplas(tuple), conjuntos(set) u objetos personalizados directamente

Así es como se ven los datos JSON:

json
{
    "name": "Alice Johnson",
    "age": 30,
    "email": "alice@example.com",
    "is_active": true,
    "scores": [85, 92, 78, 95],
    "address": {
        "street": "123 Main St",
        "city": "Springfield",
        "zip": "12345"
    }
}

Nota: Esto es texto JSON puro, no código Python. Observa el true en minúsculas y el uso de comillas dobles.

39.3.2) Convertir datos de Python a JSON con dumps()

La función dumps() (dump string) convierte estructuras de datos de Python a cadenas con formato JSON:

python
import json
 
student = {
    "name": "Alice Johnson",
    "age": 30,
    "email": "alice@example.com",
    "is_active": True,
    "scores": [85, 92, 78, 95]
}
 
# Convertir un diccionario a JSON
json_string = json.dumps(student)
print(json_string)
# Output: {"name": "Alice Johnson", "age": 30, "email": "alice@example.com", "is_active": true, "scores": [85, 92, 78, 95]}
 
print(type(json_string))  # Output: <class 'str'>

Observa cómo True de Python se convirtió en true de JSON en la salida. La función dumps() gestiona automáticamente estas conversiones.

Para una salida más legible, usa el parámetro indent:

python
import json
 
student = {
    "name": "Alice Johnson",
    "age": 30,
    "scores": [85, 92, 78, 95]
}
 
# Pretty-print con indentación
json_string = json.dumps(student, indent=2)
print(json_string)
# Output:
# {
#   "name": "Alice Johnson",
#   "age": 30,
#   "scores": [
#     85,
#     92,
#     78,
#     95
#   ]
# }

El parámetro indent especifica cuántos espacios usar para cada nivel de indentación. Esto hace que JSON sea mucho más fácil de leer, especialmente para estructuras anidadas complejas.

39.3.3) Convertir JSON a datos de Python con loads()

La función loads() (load string) convierte cadenas con formato JSON de vuelta a estructuras de datos de Python:

python
import json
 
# Cadena JSON (como podrías recibir de una API web)
json_string = '{"name": "Bob Smith", "age": 25, "scores": [90, 88, 92]}'
 
# Convertir a diccionario de Python
student = json.loads(json_string)
print(student)  # Output: {'name': 'Bob Smith', 'age': 25, 'scores': [90, 88, 92]}
print(type(student))  # Output: <class 'dict'>
 
# Acceder a los datos como cualquier diccionario de Python
print(f"Name: {student['name']}")  # Output: Name: Bob Smith
print(f"Average score: {sum(student['scores']) / len(student['scores'])}")
# Output: Average score: 90.0

true, false y null de JSON se convierten automáticamente a True, False y None de Python:

python
import json
 
json_string = '{"active": true, "verified": false, "middle_name": null}'
data = json.loads(json_string)
 
print(data)  # Output: {'active': True, 'verified': False, 'middle_name': None}
print(type(data["active"]))  # Output: <class 'bool'>
print(type(data["middle_name"]))  # Output: <class 'NoneType'>

39.3.4) Escribir JSON en archivos con dump()

La función dump() escribe datos de Python directamente en un archivo en formato JSON:

python
import json
 
# Registros de estudiantes
students = [
    {"name": "Alice", "age": 20, "gpa": 3.8},
    {"name": "Bob", "age": 22, "gpa": 3.5},
    {"name": "Charlie", "age": 21, "gpa": 3.9}
]
 
# Escribir en un archivo JSON
with open("students.json", "w") as file:
    json.dump(students, file, indent=2)
 
print("Data written to students.json")
# Output: Data written to students.json

Después de ejecutar este código, el archivo students.json contiene:

json
[
  {
    "name": "Alice",
    "age": 20,
    "gpa": 3.8
  },
  {
    "name": "Bob",
    "age": 22,
    "gpa": 3.5
  },
  {
    "name": "Charlie",
    "age": 21,
    "gpa": 3.9
  }
]

¿Por qué usar dump() en lugar de dumps()? La función dump() escribe directamente en un archivo, lo cual es más eficiente que convertir primero a una cadena y luego escribir esa cadena. Usa dump() para archivos y dumps() cuando necesites el JSON como cadena (por ejemplo, para enviarlo por una red).

39.3.5) Leer JSON desde archivos con load()

La función load() lee datos JSON desde un archivo y los convierte a estructuras de datos de Python:

python
import json
 
# Leer desde el archivo JSON que creamos antes
with open("students.json", "r") as file:
    students = json.load(file)
 
print(f"Loaded {len(students)} students")  # Output: Loaded 3 students
 
# Trabajar con los datos
for student in students:
    print(f"{student['name']}: GPA {student['gpa']}")
# Output:
# Alice: GPA 3.8
# Bob: GPA 3.5
# Charlie: GPA 3.9

39.3.6) Manejar errores de JSON

Al trabajar con JSON, podrías encontrarte con datos inválidos. Maneja siempre posibles errores:

python
import json
 
# JSON inválido: falta la comilla de cierre
invalid_json = '{"name": "Alice", "age": 30'
 
try:
    data = json.loads(invalid_json)
except json.JSONDecodeError as e:
    print(f"Invalid JSON: {e}")
    # Output: Invalid JSON: Expecting ',' delimiter: line 1 column 28 (char 27)

Esto es especialmente importante al leer JSON desde fuentes externas (archivos, APIs web, entrada del usuario) donde no puedes garantizar que los datos sean válidos:

python
import json
 
def load_config(filename):
    """Cargar la configuración desde un archivo JSON con manejo de errores"""
    try:
        with open(filename, "r") as file:
            config = json.load(file)
            return config
    except FileNotFoundError:
        print(f"Config file '{filename}' not found")
        return None
    except json.JSONDecodeError as e:
        print(f"Invalid JSON in '{filename}': {e}")
        return None
 
# Intentar cargar la configuración
config = load_config("config.json")
if config:
    print(f"Configuration loaded: {config}")
else:
    print("Using default configuration")

39.3.7) Ejemplo práctico de JSON: guardar y cargar el estado de la aplicación

Aquí tienes un ejemplo completo que muestra cómo guardar y cargar datos de una aplicación:

python
import json
 
def save_game_state(filename, player_data):
    """Guardar el estado del juego en un archivo JSON"""
    with open(filename, "w") as file:
        json.dump(player_data, file, indent=2)
    print(f"Game saved to {filename}")
 
def load_game_state(filename):
    """Cargar el estado del juego desde un archivo JSON"""
    try:
        with open(filename, "r") as file:
            player_data = json.load(file)
        print(f"Game loaded from {filename}")
        return player_data
    except FileNotFoundError:
        print("No saved game found")
        return None
 
# Datos del juego
player = {
    "name": "Hero",
    "level": 5,
    "health": 85,
    "inventory": ["sword", "shield", "potion"],
    "position": {"x": 10, "y": 20}
}
 
# Guardar el juego
save_game_state("savegame.json", player)
# Output: Game saved to savegame.json
 
# Más tarde, cargar el juego
loaded_player = load_game_state("savegame.json")
# Output: Game loaded from savegame.json
 
if loaded_player:
    print(f"Welcome back, {loaded_player['name']}!")
    print(f"Level: {loaded_player['level']}, Health: {loaded_player['health']}")
    # Output:
    # Welcome back, Hero!
    # Level: 5, Health: 85

Módulo json

dumps: Python → cadena JSON

loads: cadena JSON → Python

dump: Python → archivo JSON

load: archivo JSON → Python

Parámetro indent para legibilidad

Gestiona conversiones de tipo

Más eficiente que dumps + write

Manejar JSONDecodeError

39.4) Contenedores prácticos en collections

El módulo collections proporciona tipos de contenedores especializados que amplían los contenedores integrados de Python (listas(list), diccionarios(dict), conjuntos(set)) con funcionalidad adicional. Estos contenedores resuelven problemas comunes de forma más elegante que usando estructuras de datos básicas.

39.4.1) Contar elementos con Counter

La clase Counter está diseñada para contar objetos hashables. Es una subclase de diccionario(dict) que almacena elementos como claves y sus conteos como valores.

Qué acepta Counter como entrada:

  • Cualquier iterable (lista(list), cadena(string), tupla(tuple), etc.)
  • Otro diccionario(dict) con conteos
  • Argumentos con nombre (keyword arguments) con conteos

Qué almacena Counter:

  • Un diccionario(dict) donde las claves son los elementos y los valores son sus conteos
  • Ejemplo: Counter(['a', 'b', 'a']) almacena {'a': 2, 'b': 1}

Ventaja clave frente a diccionarios(dict) normales:

  • Devuelve 0 para claves faltantes en lugar de lanzar KeyError
  • Proporciona métodos específicos de conteo como most_common()
  • Admite operaciones aritméticas entre contadores

Uso básico

python
from collections import Counter
 
# Contar letras en una palabra
word = "mississippi"
letter_counts = Counter(word)
print(letter_counts)
# Output: Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
 
# Acceder a los conteos como un diccionario
print(f"Number of 'i's: {letter_counts['i']}")
# Output: Number of 'i's: 4
 
print(f"Number of 'z's: {letter_counts['z']}")
# Output: Number of 'z's: 0 (returns 0 for missing keys, no KeyError!)

Crear Counters a partir de distintas fuentes

python
from collections import Counter
 
# Desde una lista
votes = ["Alice", "Bob", "Alice", "Charlie", "Alice", "Bob", "Alice"]
vote_counts = Counter(votes)
print(vote_counts)
# Output: Counter({'Alice': 4, 'Bob': 2, 'Charlie': 1})
 
# Desde una cadena (cuenta cada carácter)
letter_counts = Counter("hello")
print(letter_counts)
# Output: Counter({'l': 2, 'h': 1, 'e': 1, 'o': 1})
 
# Desde un diccionario
existing_counts = {'apple': 3, 'banana': 2}
fruit_counts = Counter(existing_counts)
print(fruit_counts)
# Output: Counter({'apple': 3, 'banana': 2})
 
# Desde keyword arguments
color_counts = Counter(red=5, blue=3, green=2)
print(color_counts)
# Output: Counter({'red': 5, 'blue': 3, 'green': 2})

Encontrar los elementos más comunes con most_common()

Firma del método: most_common(n=None)

Parámetros:

  • n (opcional): Número de elementos más comunes a devolver
  • Si n se omite o es None, devuelve todos los elementos

Devuelve:

  • Una lista(list) de tuplas(tuple) (item, count)
  • Ordenada por conteo, de mayor a menor
  • Si los conteos son iguales, los elementos están en el orden en que se encontraron por primera vez
python
from collections import Counter
 
# Analizar la frecuencia de palabras en el texto
text = "the quick brown fox jumps over the lazy dog the fox"
words = text.split()
word_counts = Counter(words)
 
# Obtener las 3 palabras más comunes
top_3 = word_counts.most_common(3)
print(top_3)
# Output: [('the', 3), ('fox', 2), ('quick', 1)]

Operaciones aritméticas con Counters

Puedes sumar, restar y realizar otras operaciones sobre objetos Counter:

python
from collections import Counter
 
# Contar elementos en dos grupos
group1 = Counter(["apple", "banana", "apple", "orange"])
print(group1)
# Output: Counter({'apple': 2, 'banana': 1, 'orange': 1})
 
group2 = Counter(["banana", "banana", "grape", "apple"])
print(group2)
# Output: Counter({'banana': 2, 'grape': 1, 'apple': 1})
 
# Sumar los conteos
combined = group1 + group2
print(combined)
# Output: Counter({'apple': 3, 'banana': 3, 'orange': 1, 'grape': 1})
 
# Restar conteos (solo conserva resultados positivos)
difference = group1 - group2
print(difference)
# Output: Counter({'apple': 1, 'orange': 1})
# banana: 1 - 2 = -1 (negative, so excluded)
# grape: not in group1, so excluded

Ejemplo práctico: analizar calificaciones de estudiantes

python
from collections import Counter
 
# Distribución de calificaciones
grades = ["A", "B", "A", "C", "B", "A", "B", "D", "A", "B", "C", "A"]
grade_counts = Counter(grades)
 
print(f"Total students: {len(grades)}")
# Output: Total students: 12
 
print("\nGrade Distribution:")
for grade, count in grade_counts.most_common():
    percentage = (count / len(grades)) * 100
    bar = "█" * count
    print(f"  {grade}: {count} students ({percentage:4.1f}%) {bar}")
# Output:
# Grade Distribution:
#   A: 5 students (41.7%) █████
#   B: 4 students (33.3%) ████
#   C: 2 students (16.7%) ██
#   D: 1 students ( 8.3%) █

39.4.2) Diccionarios con valores predeterminados usando defaultdict

La clase defaultdict es una subclase de diccionario(dict) que crea automáticamente entradas con un valor por defecto cuando accedes a una clave inexistente. Esto elimina la necesidad de comprobar si las claves existen antes de usarlas.

Qué acepta defaultdict como entrada:

  • Una función de fábrica predeterminada (default factory) (obligatorio): Un callable que devuelve el valor predeterminado para claves faltantes
  • Cualquier argumento que un dict normal acepte (pares clave-valor, otro diccionario, keyword arguments)

Ventaja clave frente a diccionarios(dict) normales:

  • No hace falta comprobar si una clave existe antes de usarla
  • Inicializa automáticamente las claves faltantes con un valor predeterminado
  • Código más limpio y legible para operaciones de agrupación, conteo y acumulación

Comprender la fábrica predeterminada

Cuando creas un defaultdict, debes proporcionar una fábrica predeterminada (default factory): un callable (función) que no recibe argumentos y devuelve el valor por defecto. Fábricas predeterminadas comunes:

  • int - devuelve 0 (útil para contar)
  • list - devuelve [] (útil para agrupar elementos)
  • set - devuelve set() (útil para recopilar elementos únicos)
  • str - devuelve '' (útil para concatenación de cadenas)
  • lambda: value - devuelve un valor predeterminado personalizado
python
from collections import defaultdict
 
# Distintas fábricas predeterminadas
counts = defaultdict(int)        # Las claves faltantes devuelven 0
groups = defaultdict(list)       # Las claves faltantes devuelven []
unique = defaultdict(set)        # Las claves faltantes devuelven set()
custom = defaultdict(lambda: "N/A")  # Las claves faltantes devuelven "N/A"
 
# Probar con claves faltantes
print(counts['missing'])     # Output: 0
print(groups['missing'])     # Output: []
print(unique['missing'])     # Output: set()
print(custom['missing'])     # Output: N/A

Uso básico: conteo con defaultdict

Compara un diccionario normal vs defaultdict para contar:

python
from collections import defaultdict
 
word = "mississippi"
 
# Diccionario normal: hay que comprobar si la clave existe
regular_dict = {}
for letter in word:
    if letter not in regular_dict:
        regular_dict[letter] = 0
    regular_dict[letter] += 1
 
print(regular_dict)
# Output: {'m': 1, 'i': 4, 's': 4, 'p': 2}
 
# defaultdict: crea automáticamente entradas con valor por defecto
letter_counts = defaultdict(int)  # int() devuelve 0
for letter in word:
    letter_counts[letter] += 1  # ¡No hace falta comprobar si la clave existe!
 
print(dict(letter_counts))
# Output: {'m': 1, 'i': 4, 's': 4, 'p': 2}

Cómo funciona:

  1. Cuando accedes a letter_counts[letter] para una letra nueva, defaultdict llama a int() que devuelve 0
  2. Se crea la clave con valor 0, luego += 1 lo convierte en 1
  3. Para claves existentes, se comporta como un diccionario normal

Agrupar elementos con defaultdict(list)

Un caso de uso común es agrupar elementos en categorías:

python
from collections import defaultdict
 
students = [
    ("Alice", "A"),
    ("Bob", "B"),
    ("Charlie", "A"),
    ("Diana", "C"),
    ("Eve", "B"),
    ("Frank", "A")
]
 
# Agrupar estudiantes por calificación
# Con defaultdict: limpio y sencillo
students_by_grade = defaultdict(list)
for name, grade in students:
    students_by_grade[grade].append(name)
 
print(dict(students_by_grade))
# Output: {'A': ['Alice', 'Charlie', 'Frank'], 'B': ['Bob', 'Eve'], 'C': ['Diana']}
 
# Acceder a una calificación que aún no existe
print(students_by_grade["D"])  # Output: [] (empty list, not KeyError!)

Cómo funciona:

  1. Cuando accedes a students_by_grade[grade] para una calificación nueva, defaultdict llama a list() que devuelve []
  2. Se crea la clave con una lista vacía, luego .append(name) añade el primer estudiante
  3. Para calificaciones existentes, simplemente añade a la lista existente

Crear defaultdict a partir de un diccionario existente

Puedes inicializar un defaultdict con datos existentes:

python
from collections import defaultdict
 
# Empezar con conteos existentes
existing_data = {'apple': 5, 'banana': 3}
 
# Crear defaultdict desde un diccionario existente
fruit_counts = defaultdict(int, existing_data)
 
# Añadir más conteos
fruit_counts['apple'] += 2     # 5 + 2 = 7
fruit_counts['orange'] += 1    # 0 + 1 = 1 (new key, starts at 0)
 
print(dict(fruit_counts))
# Output: {'apple': 7, 'banana': 3, 'orange': 1}

Fábrica predeterminada personalizada

Puedes proporcionar cualquier callable como fábrica predeterminada:

python
from collections import defaultdict
 
# Usar lambda para valores predeterminados personalizados
page_views = defaultdict(lambda: {'views': 0, 'unique': 0})
 
page_views['home']['views'] = 100
page_views['home']['unique'] = 75
 
print(page_views['home'])
# Output: {'views': 100, 'unique': 75}
 
print(page_views['about'])  # New key gets default dictionary
# Output: {'views': 0, 'unique': 0}

Notas importantes

Acceder vs. comprobar claves:

python
from collections import defaultdict
 
counts = defaultdict(int)
 
# Acceder a una clave faltante la CREA
value = counts['missing']  # Creates 'missing' with value 0
print('missing' in counts)  # Output: True
 
# Para comprobar sin crear, usa 'in' o .get()
counts2 = defaultdict(int)
print('missing' in counts2)      # Output: False (doesn't create key)
print(counts2.get('missing'))    # Output: None (doesn't create key)

39.5) (Opcional) Herramientas útiles de iteración

El módulo itertools proporciona funciones para crear iteradores eficientes. Estas herramientas te ayudan a trabajar con secuencias de formas potentes sin crear grandes listas(list) intermedias.

39.5.1) Encadenar iterables con chain()

La función chain() combina varios iterables en un único iterador que produce elementos de cada iterable en secuencia.

Qué acepta chain():

  • Varios iterables (listas(list), tuplas(tuple), cadenas(string), etc.) como argumentos separados

Qué devuelve chain():

  • Un iterador que produce todos los elementos del primer iterable, luego todos los elementos del segundo, y así sucesivamente

Ventaja clave:

  • Más eficiente en memoria que concatenar con + (no crea listas intermedias)
  • Funciona con cualquier iterable, no solo con listas
python
from itertools import chain
 
# Combinar varias listas
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
 
combined = chain(list1, list2, list3)
print(list(combined))  # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

Esto es más eficiente en memoria que concatenar listas con +, especialmente para secuencias grandes:

python
from itertools import chain
 
# Procesar varias fuentes de datos sin crear una gran lista combinada
students_class_a = ["Alice", "Bob", "Charlie"]
students_class_b = ["Diana", "Eve", "Frank"]
students_class_c = ["Grace", "Henry", "Iris"]
 
# Iterar sobre todos los estudiantes sin crear una lista combinada
for student in chain(students_class_a, students_class_b, students_class_c):
    print(f"Processing: {student}")
# Output:
# Processing: Alice
# Processing: Bob
# Processing: Charlie
# Processing: Diana
# Processing: Eve
# Processing: Frank
# Processing: Grace
# Processing: Henry
# Processing: Iris

Puedes encadenar distintos tipos de iterables:

python
from itertools import chain
 
# Encadenar listas, tuplas y cadenas
numbers = [1, 2, 3]
letters = ("a", "b", "c")
word = "xyz"
 
combined = chain(numbers, letters, word)
print(list(combined))  # Output: [1, 2, 3, 'a', 'b', 'c', 'x', 'y', 'z']

39.5.2) Repetir elementos con cycle()

La función cycle() crea un iterador infinito que recorre repetidamente los elementos de un iterable.

Qué acepta cycle():

  • Un único iterable (lista(list), tupla(tuple), cadena(string), etc.)

Qué devuelve cycle():

  • Un iterador infinito que produce elementos del iterable repetidamente
  • Después de llegar al final, empieza de nuevo desde el principio

Características clave:

  • Crea un iterador infinito: nunca se detiene por sí solo
  • Debe usarse con una condición de parada (contador, break o zip())
  • Eficiente en memoria: no crea copias de los datos
python
from itertools import cycle
 
# Crear un ciclo infinito de colores
colors = cycle(["red", "green", "blue"])
 
# Tomar los primeros 10 colores
for i, color in enumerate(colors):
    if i >= 10:
        break
    print(f"Item {i}: {color}")
# Output:
# Item 0: red
# Item 1: green
# Item 2: blue
# Item 3: red
# Item 4: green
# Item 5: blue
# Item 6: red
# Item 7: green
# Item 8: blue
# Item 9: red

Advertencia: cycle() crea un iterador infinito. Úsalo siempre con una condición de parada (como un contador o una sentencia break), o crearás un bucle(loop) infinito.

Un caso de uso práctico es alternar entre valores:

python
from itertools import cycle
 
# Alternar entre dos colores de fondo para filas de una tabla
row_colors = cycle(["white", "lightgray"])
 
rows = ["Row 1", "Row 2", "Row 3", "Row 4", "Row 5"]
for row, color in zip(rows, row_colors):
    print(f"{row}: background-color: {color}")
# Output:
# Row 1: background-color: white
# Row 2: background-color: lightgray
# Row 3: background-color: white
# Row 4: background-color: lightgray
# Row 5: background-color: white

Aquí usamos zip() (que aprendimos en el Capítulo 37) para emparejar cada fila con un color. El iterador cycle() repite automáticamente los colores según sea necesario.

39.5.3) Combinar chain() y cycle()

Puedes combinar funciones de itertools para patrones más complejos:

python
from itertools import chain, cycle
 
# Crear un patrón que cicla entre varias secuencias
pattern1 = [1, 2, 3]
pattern2 = [10, 20]
 
# Encadenar los patrones y luego ciclar el resultado
combined_pattern = cycle(chain(pattern1, pattern2))
 
# Tomar los primeros 12 valores
for i, value in enumerate(combined_pattern):
    if i >= 12:
        break
    print(value, end=" ")
# Output: 1 2 3 10 20 1 2 3 10 20 1 2
 
print()  # Newline

Esto crea un patrón repetitivo: 1, 2, 3, 10, 20, 1, 2, 3, 10, 20, ...

Aquí tienes un ejemplo práctico para crear un calendario rotativo:

python
from itertools import cycle
 
# Crear un calendario rotativo para miembros del equipo
team_members = ["Alice", "Bob", "Charlie"]
schedule = cycle(team_members)
 
# Asignar tareas a los miembros del equipo de forma rotativa
tasks = [
    "Review code",
    "Write tests",
    "Update documentation",
    "Fix bug #123",
    "Implement feature X",
    "Deploy to staging"
]
 
print("Task Assignments:")
for task, assignee in zip(tasks, schedule):
    print(f"  {assignee}: {task}")
# Output:
# Task Assignments:
#   Alice: Review code
#   Bob: Write tests
#   Charlie: Update documentation
#   Alice: Fix bug #123
#   Bob: Implement feature X
#   Charlie: Deploy to staging

Módulo itertools

chain: Combinar iterables

cycle: Repetir para siempre

Más eficiente en memoria que +

Funciona con distintos tipos

Crea un iterador infinito

Usar siempre con condición de parada

Útil para patrones alternos


En este capítulo, exploramos cinco módulos esenciales de la biblioteca estándar que amplían las capacidades de Python:

  • random: Genera números aleatorios, haz selecciones aleatorias y baraja secuencias: esencial para simulaciones, juegos y pruebas
  • datetime: Trabaja con fechas, horas y duraciones: calcula edades, programa eventos y da formato a marcas de tiempo
  • json: Intercambia datos con otros programas usando el formato universal JSON: guarda el estado de la aplicación, trabaja con APIs web y almacena configuración
  • collections: Usa contenedores especializados como Counter para contar y defaultdict para crear claves automáticamente
  • itertools: Crea iteradores eficientes con chain() para combinar secuencias y cycle() para repetir patrones

Estos módulos forman parte de la biblioteca estándar de Python: siempre están disponibles, están bien probados y resuelven problemas comunes de programación de forma elegante. A medida que construyas programas más complejos, verás que recurres a estas herramientas con frecuencia. Representan la filosofía de Python de "batteries included": proporcionar soluciones potentes y listas para usar para tareas de programación cotidianas.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai