🐍 Python Aumentado: El Libro que los Otros 9 Millones No Escribieron
El libro de Python más completo de 2026: desde cero hasta producción, con IA integrada en cada capítulo. Ciberseguridad, Data Science, automatización, desarrollo web, y APIs de LLMs. Siete partes, código real, secretos que nadie más te cuenta, y la IA como copiloto permanente.
Jafet Brito
Security Researcher
🐍 Python Aumentado: El Libro que los Otros 9 Millones No Escribieron
Por Jafet Brito · Security Researcher · Publicado el 11 de junio de 2026
“Python no es un lenguaje de programación. Es el idioma con el que la humanidad le habla a las máquinas en 2026.”
“El código que no puedes leer en seis meses no era código — era magia oscura.” — El principio de la legibilidad de Python
⚠️ Por Qué Este Libro es Diferente
Hay 9 millones de tutoriales de Python en internet. Este no es uno más.
Lo que hace diferente a este libro:
- 🤖 IA como copiloto permanente — cada capítulo incluye cómo usar Claude, Gemini, o GitHub Copilot para acelerar ese tema específico
- 🔐 Mentalidad Zero Trust desde el primer día — seguridad no es un capítulo al final, es una filosofía que atraviesa todo el código
- 🎯 Proyectos reales, no ejercicios de juguete — cada parte termina con algo que puedes poner en tu portafolio
- 📅 Python 3.14 + las novedades de 2026 — t-strings, free-threading, JIT compiler, y lo que realmente importa
- 🌐 El puente hacia los LLMs — Python como la puerta de entrada al ecosistema de IA generativa
- ⚗️ Los “secretos” que los tutoriales básicos nunca mencionan — patrones de producción, anti-patterns, y las decisiones que diferencian código amateur de código profesional
🗺️ Mapa del Libro
📖 PYTHON AUMENTADO — ESTRUCTURA COMPLETA
PARTE 0: Antes del Código — La Mentalidad
└─ Cap 0.1: Por qué Python ganó
└─ Cap 0.2: Cómo configurar tu entorno como un profesional
└─ Cap 0.3: Tu primer copiloto de IA
PARTE I: Los Fundamentos (Sin Atajos)
└─ Cap 1.1: Variables, tipos y la verdad sobre los objetos
└─ Cap 1.2: Operadores, condiciones y el arte de la decisión
└─ Cap 1.3: Loops — la filosofía del control
└─ Cap 1.4: Funciones — el arte de no repetirte
└─ Cap 1.5: Errores y excepciones — Zero Trust en tu código
PARTE II: Estructuras de Datos — Los Bloques de la Realidad
└─ Cap 2.1: Listas — el array que sabe demasiado
└─ Cap 2.2: Diccionarios — el JSON antes de JSON
└─ Cap 2.3: Sets y Tuplas — cuando importa la semántica
└─ Cap 2.4: Comprensiones — Python idiomático
PARTE III: Python Intermedio — Donde Todo Cambia
└─ Cap 3.1: Programación Orientada a Objetos
└─ Cap 3.2: Decoradores — el patrón que nadie explica bien
└─ Cap 3.3: Generators e Iterators — memoria infinita finita
└─ Cap 3.4: Context Managers y el protocolo with
└─ Cap 3.5: Módulos, paquetes y el arte de organizar código
└─ Cap 3.6: Python 3.14 — las novedades que importan
PARTE IV: Python para el Mundo Real
└─ Cap 4.1: Archivos, paths y el sistema de archivos
└─ Cap 4.2: APIs REST — habla con el mundo
└─ Cap 4.3: Bases de datos con Python
└─ Cap 4.4: Testing — código que confía en sí mismo
└─ Cap 4.5: Concurrencia y paralelismo moderno
PARTE V: Python + IA — El Diferenciador de 2026
└─ Cap 5.1: El ecosistema de IA en Python
└─ Cap 5.2: APIs de LLMs — Claude, GPT, Gemini desde código
└─ Cap 5.3: LangChain y agentes de IA
└─ Cap 5.4: Data Science esencial (NumPy, Pandas, visualización)
└─ Cap 5.5: Machine Learning con scikit-learn
└─ Cap 5.6: Deep Learning con PyTorch
PARTE VI: Python para Ciberseguridad y OSINT
└─ Cap 6.1: Python para seguridad — mentalidad y ética
└─ Cap 6.2: Networking y análisis de paquetes
└─ Cap 6.3: OSINT automatizado con Python
└─ Cap 6.4: Automatización de herramientas de seguridad
└─ Cap 6.5: Python para Blue Team — detección y análisis
PARTE VII: Python en Producción
└─ Cap 7.1: Entornos virtuales y gestión de dependencias
└─ Cap 7.2: Docker + Python
└─ Cap 7.3: CI/CD para proyectos Python
└─ Cap 7.4: APIs con FastAPI
└─ Cap 7.5: El portafolio que consigue empleo
PARTE 0: Antes del Código — La Mentalidad
Capítulo 0.1: Por Qué Python Ganó
Antes de escribir una sola línea de código, necesitas entender por qué Python. No porque sea el “mejor” lenguaje — no lo es en velocidad pura, no lo es en tipado estático, no lo es en consumo de memoria. Python ganó por razones más profundas:
1. La filosofía de la legibilidad
Python tiene un documento llamado PEP 20 — The Zen of Python. Escribe import this en cualquier intérprete de Python y lo verás. Los principios más importantes:
“Beautiful is better than ugly.” “Explicit is better than implicit.” “Simple is better than complex.” “Readability counts.”
Esto no es marketing. Es la filosofía que guía cada decisión de diseño del lenguaje. Python se diseñó para que los humanos lo lean primero, las máquinas después.
2. El ecosistema que ganó todas las guerras
En 2026, Python domina:
- 🧠 Machine Learning / IA — PyTorch, TensorFlow, scikit-learn, Hugging Face
- 📊 Data Science — Pandas, NumPy, Jupyter, Matplotlib
- 🔐 Ciberseguridad — Scapy, Impacket, herramientas de pentest
- 🌐 Web backends — Django, FastAPI, Flask
- ⚙️ Automatización — Ansible, scripts de infraestructura, Selenium
- 🤖 Integración con LLMs — El lenguaje nativo de Claude, GPT y Gemini
Cuando Google, Netflix, Instagram, Spotify, o cualquier empresa de IA escribe código, escriben Python. No porque sea el más rápido — porque es el más comunicativo.
3. Python 3.14 — El momento del salto de rendimiento
La versión más reciente estable de Python es la 3.14 (lanzada el 7 de octubre de 2025). Las novedades que realmente importan para 2026:
- 🚀 Free-Threading oficial (PEP 779) — se elimina el Global Interpreter Lock (GIL), habilitando verdadero paralelismo en múltiples hilos
- ⚡ JIT Compiler experimental — aceleración de rendimiento para loops intensivos
- 🧵 T-strings (PEP 750) — evolución de los f-strings con procesamiento personalizable
- 📝 Anotaciones diferidas — mejoran tiempo de inicio y rendimiento en runtime
- 🎨 REPL con colores y multi-línea — la experiencia interactiva que Python siempre debió tener
- 🆔 UUID v7 — nativo en el módulo
uuid, ordenados por tiempo de creación
Capítulo 0.2: Configura Tu Entorno Como un Profesional
La mayoría de tutoriales te dice “instala Python”. Los profesionales hacen algo diferente.
El Stack de Desarrollo Profesional 2026
# Paso 1: Instala pyenv (gestiona múltiples versiones de Python)
# En macOS/Linux:
curl https://pyenv.run | bash
# En Windows (con PowerShell como admin):
# Instala desde https://github.com/pyenv-win/pyenv-win
# Paso 2: Instala Python 3.14 con pyenv
pyenv install 3.14.0
pyenv global 3.14.0
# Verifica
python --version # Python 3.14.0
# Paso 3: Instala uv — el gestor de paquetes moderno
# uv es 10-100x más rápido que pip
curl -LsSf https://astral.sh/uv/install.sh | sh
# Paso 4: Crea tu primer proyecto profesional
uv init mi-proyecto
cd mi-proyecto
uv add requests # equivalente a pip install, pero mejor
# Paso 5: Editor de código
# VS Code con las extensiones:
# - Python (Microsoft)
# - Pylance (autocompletado avanzado)
# - Ruff (linter ultrarrápido)
# - GitHub Copilot (tu copiloto de IA)
La Estructura de Proyecto Profesional
mi-proyecto/
├── src/
│ └── mi_proyecto/
│ ├── __init__.py
│ ├── main.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ └── test_main.py
├── .env # Variables de entorno (NUNCA en git)
├── .env.example # Ejemplo sin valores reales
├── .gitignore
├── pyproject.toml # Configuración moderna del proyecto
└── README.md
🔐 Zero Trust desde el día 1: El archivo
.envcontiene tus credenciales y claves API. Nunca lo subas a git. Crea siempre un.gitignoreque lo excluya antes de escribir tu primera línea de código.
# Crea .gitignore antes de git init
echo ".env" >> .gitignore
echo "__pycache__/" >> .gitignore
echo ".venv/" >> .gitignore
echo "*.pyc" >> .gitignore
Capítulo 0.3: Tu Primer Copiloto de IA
Antes de empezar a aprender Python, configura tu copiloto. En 2026, aprender a programar sin IA es como aprender a cocinar sin saber que existen los cuchillos afilados.
Las 3 Formas de Usar IA para Aprender Python
Modo 1 — El Tutor (para entender conceptos)
Prompt para Claude/Gemini/GPT:
"Soy principiante en Python. Explícame qué es
una función como si fuera un chef que hace
recetas reutilizables. Luego dame un ejemplo
de una función que calcule el precio con IVA,
y explícame cada línea. Al final, hazme 3
preguntas para verificar que entendí."
Modo 2 — El Par Programador (para escribir código)
Prompt para GitHub Copilot / Cursor:
"Escribe una función Python que:
- Reciba una lista de emails
- Valide que cada uno tenga formato correcto
- Retorne una tupla: (emails_válidos, emails_inválidos)
- Incluya type hints modernos de Python 3.14
- Incluya docstring con ejemplos
- Sea testeable con pytest"
Modo 3 — El Debugger (para resolver errores)
Prompt:
"Este código Python me da el siguiente error.
En lugar de darme la solución directamente,
explícame QUÉ está causando el error y POR QUÉ,
luego hazme preguntas para guiarme a encontrar
la solución yo mismo.
Código: [tu código]
Error: [el mensaje de error completo]"
💡 El secreto de aprender con IA: Usa la IA para entender, no para copiar. El código que no entiendes es deuda cognitiva que pagarás con intereses cuando necesites modificarlo.
PARTE I: Los Fundamentos — Sin Atajos
Capítulo 1.1: Variables, Tipos y la Verdad Sobre los Objetos
La Primera Verdad: Todo es un Objeto
A diferencia de muchos lenguajes, en Python absolutamente todo es un objeto. Los números, las cadenas de texto, las funciones, las clases — todo. Esta decisión de diseño tiene consecuencias profundas que la mayoría de tutoriales para principiantes ignora completamente.
# Parece simple...
nombre = "Jafet"
edad = 28
es_activo = True
precio = 99.99
# Pero en realidad, cada variable apunta a un objeto en memoria
# Esto no es solo un número:
x = 42
print(type(x)) # <class 'int'>
print(id(x)) # Dirección en memoria: ej. 140234567890
print(x.__class__) # <class 'int'>
print(dir(x)) # Todos los métodos que tiene un int
# Los enteros pequeños son objetos compartidos (optimización)
a = 256
b = 256
print(a is b) # True — mismo objeto en memoria
a = 257
b = 257
print(a is b) # False — objetos distintos (fuera del rango cacheado)
🧠 El secreto que nadie cuenta: Python cachea los enteros del -5 al 256 como objetos singleton. Esto explica uno de los bugs más confusos que encontrarás en tu carrera: por qué
a is besTruepara números pequeños peroFalsepara grandes. Usa siempre==para comparar valores,issolo para verificar identidad de objeto (comois None).
Los Tipos Fundamentales de Python
# Tipos numéricos
entero = 42 # int — precisión arbitraria (no overflow!)
flotante = 3.14159 # float — IEEE 754 doble precisión
complejo = 3 + 4j # complex — números complejos nativos
booleano = True # bool — subclase de int (True == 1, False == 0)
# El "gotcha" del flotante que destruye aplicaciones financieras:
print(0.1 + 0.2) # 0.30000000000000004 ⚠️
# La solución profesional:
from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2')) # 0.2 ✅
# Cadenas de texto
simple = 'hola'
doble = "mundo"
multilinea = """
Este es un texto
que ocupa múltiples
líneas
"""
# F-strings — la forma moderna (Python 3.6+)
nombre = "Jafet"
edad = 28
mensaje = f"Hola, {nombre}. Tienes {edad} años."
# T-strings — LA NOVEDAD de Python 3.14
# Permiten procesamiento personalizado de strings
from string.templatelib import Template # Python 3.14
datos_usuario = t"SELECT * FROM users WHERE id = {user_id}"
# A diferencia de f-strings, t-strings no se evalúan automáticamente
# Permiten sanitización, escape, traducción, etc.
# None — el valor especial de "nada"
resultado = None
print(resultado is None) # True — siempre usa 'is' con None
# Verificación de tipos
print(isinstance(42, int)) # True
print(isinstance(3.14, (int, float))) # True — múltiples tipos
Tipado Dinámico vs Tipado Estático — La Conversación que Importa
# Python es dinámicamente tipado:
# Una variable puede cambiar de tipo durante la ejecución
x = 42
x = "ahora soy un string" # Python lo permite
x = [1, 2, 3] # Y ahora una lista
# Pero en 2026, el código Python profesional usa TYPE HINTS
# No cambia el comportamiento — mejora la legibilidad y el tooling
def calcular_iva(precio: float, tasa: float = 0.16) -> float:
"""
Calcula el precio con IVA incluido.
Args:
precio: Precio base sin IVA
tasa: Tasa de IVA (default 16%)
Returns:
Precio total con IVA
Examples:
>>> calcular_iva(100.0)
116.0
>>> calcular_iva(100.0, 0.10)
110.0
"""
return precio * (1 + tasa)
# Python 3.14 — Anotaciones diferidas: mejor rendimiento
# Las anotaciones ya no se evalúan en tiempo de importación
from __future__ import annotations # En 3.13 aún necesario, nativo en 3.14
Capítulo 1.2: Operadores, Condiciones y el Arte de la Decisión
# Operadores de comparación — todos retornan bool
print(5 == 5) # True — igualdad de valor
print(5 != 3) # True — desigualdad
print(5 > 3) # True — mayor que
print(5 >= 5) # True — mayor o igual
# Python permite encadenar comparaciones (no todos los lenguajes lo hacen):
edad = 25
print(18 <= edad < 65) # True — pythónico y legible
# Operadores lógicos
print(True and False) # False
print(True or False) # True
print(not True) # False
# Short-circuit evaluation — crucial para rendimiento y seguridad:
usuario = None
# Esto NO lanza error porque Python evalúa de izquierda a derecha:
nombre = usuario and usuario.nombre # None (no evalúa .nombre)
nombre = usuario or "Anónimo" # "Anónimo"
# Operador walrus := (Python 3.8+) — asignar y evaluar en una línea
import re
texto = "Error: archivo no encontrado"
if match := re.search(r"Error: (.+)", texto):
print(f"Encontrado: {match.group(1)}") # archivo no encontrado
# Condicionales — la forma idiomática
def clasificar_edad(edad: int) -> str:
"""Clasifica una persona por rango de edad."""
match edad: # Pattern matching — Python 3.10+
case edad if edad < 0:
return "Edad inválida"
case edad if edad < 13:
return "Niño"
case edad if edad < 18:
return "Adolescente"
case edad if edad < 65:
return "Adulto"
case _:
return "Adulto mayor"
# Expresión ternaria — para condicionales simples en una línea
status = "activo" if usuario else "inactivo"
# El anti-pattern más común con condicionales:
# ❌ Comparar booleanos explícitamente:
if es_activo == True: # MAL
pass
# ✅ El modo Python:
if es_activo: # BIEN
pass
Capítulo 1.3: Loops — La Filosofía del Control
# For loops — iteración sobre secuencias
frutas = ["mango", "papaya", "guanábana"]
# ❌ El loop no-Python (estilo C/Java):
for i in range(len(frutas)):
print(frutas[i])
# ✅ El loop Python:
for fruta in frutas:
print(fruta)
# ✅ Cuando necesitas el índice, usa enumerate:
for i, fruta in enumerate(frutas):
print(f"{i}: {fruta}")
# ✅ Iterar sobre dos listas simultáneamente, usa zip:
nombres = ["Ana", "Pedro", "María"]
notas = [95, 87, 92]
for nombre, nota in zip(nombres, notas):
print(f"{nombre}: {nota}")
# While loops — cuando no sabes cuántas veces iterar
intentos = 0
max_intentos = 3
while intentos < max_intentos:
# Simula verificación de credenciales
credenciales_ok = verificar_credenciales()
if credenciales_ok:
break
intentos += 1
else:
# El else en un while/for se ejecuta si NO hubo break
# Uno de los secretos más útiles de Python que nadie enseña
print("Cuenta bloqueada por demasiados intentos")
# Loop control
for numero in range(100):
if numero % 2 == 0:
continue # Salta al siguiente iteración
if numero > 10:
break # Sale del loop
print(numero)
# Loops con comprensiones — veremos más en Parte II
cuadrados = [x**2 for x in range(10)] # [0, 1, 4, 9, 16...]
pares = [x for x in range(20) if x % 2 == 0]
# 🔐 Zero Trust en loops — el peligro del loop infinito en producción:
import time
def loop_seguro(max_iteraciones: int = 10_000):
"""Loop con protección de timeout para código productivo."""
for i in range(max_iteraciones):
# Tu lógica aquí
resultado = procesar_item(i)
if resultado.completado:
break
else:
raise TimeoutError(f"Loop no convergió en {max_iteraciones} iteraciones")
Capítulo 1.4: Funciones — El Arte de No Repetirte
Las funciones son el primer nivel de abstracción. Son donde el código empieza a tener significado, no solo instrucciones.
# Función básica con todos los elementos modernos
def procesar_pago(
monto: float,
moneda: str = "MXN",
*, # Todo lo que sigue es keyword-only
descripcion: str = "",
validar: bool = True
) -> dict:
"""
Procesa un pago con validación opcional.
Args:
monto: Cantidad a cobrar (debe ser positiva)
moneda: Código ISO de moneda (default: MXN)
descripcion: Descripción opcional del pago
validar: Si True, valida el monto antes de procesar
Returns:
Diccionario con status, id_transaccion, y timestamp
Raises:
ValueError: Si el monto es negativo o cero
Examples:
>>> procesar_pago(100.0, descripcion="Suscripción mensual")
{'status': 'success', 'monto': 100.0, 'moneda': 'MXN'}
"""
if validar and monto <= 0:
raise ValueError(f"Monto debe ser positivo, recibido: {monto}")
return {
"status": "success",
"monto": monto,
"moneda": moneda,
"descripcion": descripcion
}
# *args y **kwargs — flexibilidad máxima
def log_evento(nivel: str, mensaje: str, *args, **kwargs):
"""Logger flexible que acepta cualquier contexto adicional."""
import datetime
entry = {
"timestamp": datetime.datetime.now().isoformat(),
"nivel": nivel,
"mensaje": mensaje.format(*args),
"contexto": kwargs
}
print(entry)
log_evento("ERROR", "Fallo en autenticación para {}", "usuario@email.com",
ip="192.168.1.1", intentos=3)
# Funciones lambda — para operaciones simples en una línea
ordenar_por_apellido = lambda persona: persona["apellido"]
personas_ordenadas = sorted(personas, key=ordenar_por_apellido)
# Pero en Python moderno, preferimos funciones nombradas cuando hay lógica:
from operator import itemgetter
personas_ordenadas = sorted(personas, key=itemgetter("apellido")) # Más rápido
# Funciones como ciudadanos de primera clase (first-class functions)
def aplicar_descuento(precio: float) -> float:
return precio * 0.9
def aplicar_iva(precio: float) -> float:
return precio * 1.16
def calcular_precio_final(precio: float, *transformaciones) -> float:
"""Aplica una cadena de transformaciones de precio."""
resultado = precio
for transformacion in transformaciones:
resultado = transformacion(resultado)
return resultado
precio = calcular_precio_final(100.0, aplicar_descuento, aplicar_iva)
print(f"Precio final: ${precio:.2f}") # $104.40
Capítulo 1.5: Errores y Excepciones — Zero Trust en Tu Código
Principio Zero Trust aplicado al código: No asumas que los datos externos son válidos. No asumas que la red está disponible. No asumas que el archivo existe. Verifica todo.
# La jerarquía de excepciones en Python
# BaseException
# └── Exception
# ├── ValueError — valor incorrecto
# ├── TypeError — tipo incorrecto
# ├── KeyError — clave no existe en dict
# ├── IndexError — índice fuera de rango
# ├── AttributeError — atributo no existe
# ├── FileNotFoundError — archivo no existe
# ├── PermissionError — sin permisos
# └── ...
# Manejo básico de excepciones
def leer_configuracion(ruta: str) -> dict:
"""Lee un archivo JSON de configuración de forma segura."""
import json
import pathlib
ruta_path = pathlib.Path(ruta)
try:
contenido = ruta_path.read_text(encoding="utf-8")
return json.loads(contenido)
except FileNotFoundError:
print(f"⚠️ Archivo no encontrado: {ruta}")
return {}
except json.JSONDecodeError as e:
print(f"⚠️ JSON inválido en {ruta}: {e}")
return {}
except PermissionError:
print(f"🔒 Sin permisos para leer: {ruta}")
raise # Re-lanza la excepción — no podemos continuar sin permisos
finally:
# Se ejecuta SIEMPRE, independientemente del resultado
print(f"Intento de lectura completado para: {ruta}")
# Crear excepciones personalizadas — crucial para código productivo
class ErrorAutenticacion(Exception):
"""Excepción para fallos de autenticación."""
def __init__(self, usuario: str, razon: str):
self.usuario = usuario
self.razon = razon
super().__init__(f"Fallo de auth para '{usuario}': {razon}")
class ErrorRateLimiting(ErrorAutenticacion):
"""Cuenta bloqueada por demasiados intentos."""
def __init__(self, usuario: str, tiempo_espera: int):
super().__init__(usuario, f"Rate limit — espera {tiempo_espera}s")
self.tiempo_espera = tiempo_espera
# Uso:
try:
autenticar("hacker@evil.com")
except ErrorRateLimiting as e:
print(f"Cuenta bloqueada. Intenta en {e.tiempo_espera} segundos.")
except ErrorAutenticacion as e:
print(f"No autorizado: {e.razon}")
# Context manager para manejo de recursos (más sobre esto en Parte III)
# El with garantiza que los recursos se limpien aunque haya error
with open("datos.txt", "r") as archivo:
contenido = archivo.read()
# El archivo se cierra automáticamente aquí, incluso si hubo excepción
PARTE II: Estructuras de Datos — Los Bloques de la Realidad
Capítulo 2.1: Listas — El Array que Sabe Demasiado
# Operaciones fundamentales con listas
frutas = ["mango", "papaya", "guanábana", "tamarindo"]
# Slicing — una de las operaciones más poderosas de Python
print(frutas[1:3]) # ["papaya", "guanábana"] — de índice 1 a 2
print(frutas[:2]) # ["mango", "papaya"] — desde el inicio
print(frutas[2:]) # ["guanábana", "tamarindo"] — hasta el final
print(frutas[-1]) # "tamarindo" — el último elemento
print(frutas[::-1]) # Invierte la lista — el truco que sorprende
# Métodos importantes
frutas.append("mamey") # Añade al final — O(1)
frutas.insert(1, "pitahaya") # Inserta en posición — O(n)
frutas.pop() # Remueve y retorna el último — O(1)
frutas.pop(0) # Remueve y retorna el primero — O(n)
frutas.remove("papaya") # Remueve primera ocurrencia — O(n)
# Ordenamiento
numeros = [3, 1, 4, 1, 5, 9, 2, 6]
numeros.sort() # Ordena in-place (modifica la lista)
ordenados = sorted(numeros) # Retorna nueva lista ordenada
descendente = sorted(numeros, reverse=True)
# Ordenar objetos complejos
personas = [
{"nombre": "Ana", "edad": 28},
{"nombre": "Pedro", "edad": 22},
{"nombre": "María", "edad": 35}
]
por_edad = sorted(personas, key=lambda p: p["edad"])
por_nombre = sorted(personas, key=lambda p: p["nombre"])
# El secreto de rendimiento: las listas son O(n) para búsquedas
# Para búsquedas frecuentes, usa sets o diccionarios:
emails_lista = ["a@b.com", "c@d.com", "e@f.com"]
emails_set = set(emails_lista) # Convierte a set para O(1) lookup
if "a@b.com" in emails_lista: # O(n) — recorre toda la lista
pass
if "a@b.com" in emails_set: # O(1) — tabla hash directa
pass
# Listas como stacks y queues
stack = []
stack.append(1) # push
stack.append(2)
ultimo = stack.pop() # pop — eficiente
from collections import deque # Para queues eficientes
queue = deque()
queue.append(1) # enqueue — O(1)
queue.append(2)
primero = queue.popleft() # dequeue — O(1) (vs O(n) en list)
Capítulo 2.2: Diccionarios — El JSON Antes de JSON
Los diccionarios son la estructura de datos más poderosa y más usada de Python. En 2026, también son la interfaz natural entre Python y el mundo de los LLMs (que trabajan con JSON).
# Diccionario básico
usuario = {
"id": 12345,
"nombre": "Jafet Brito",
"email": "jafet@example.com",
"roles": ["admin", "researcher"],
"activo": True
}
# Acceso seguro — el error más común en Python es KeyError
print(usuario["nombre"]) # Lanza KeyError si no existe
print(usuario.get("telefono")) # None si no existe — SEGURO
print(usuario.get("telefono", "N/A")) # "N/A" si no existe — MEJOR
# Modificación
usuario["nombre"] = "Jafet B." # Modifica
usuario["creado"] = "2026-06-11" # Añade nueva clave
del usuario["telefono"] # Elimina (cuidado con KeyError)
usuario.pop("telefono", None) # Elimina sin KeyError si no existe
# Iteración sobre diccionarios
for clave in usuario: # Itera sobre claves
print(clave)
for clave, valor in usuario.items(): # Itera sobre pares — PREFERIDO
print(f"{clave}: {valor}")
claves = list(usuario.keys()) # Lista de claves
valores = list(usuario.values()) # Lista de valores
# Merge de diccionarios — el operador | (Python 3.9+)
base = {"host": "localhost", "puerto": 5432}
override = {"puerto": 5433, "database": "mi_db"}
config = base | override # {"host": "localhost", "puerto": 5433, "database": "mi_db"}
# defaultdict — evita KeyError en conteo y agrupación
from collections import defaultdict
conteo = defaultdict(int)
palabras = "python es genial python es rapido".split()
for palabra in palabras:
conteo[palabra] += 1 # No lanza KeyError aunque la clave no exista
# {"python": 2, "es": 2, "genial": 1, "rapido": 1}
# Counter — para contar elementos
from collections import Counter
frecuencia = Counter(palabras)
print(frecuencia.most_common(3)) # Los 3 más frecuentes
# Diccionarios anidados — representación de datos complejos
config_sistema = {
"database": {
"host": "localhost",
"puerto": 5432,
"credenciales": {
"usuario": "admin",
"password": "NO_HARDCODEAR_ESTO" # Usar env vars
}
},
"api": {
"base_url": "https://api.example.com",
"timeout": 30
}
}
# Acceso profundo seguro con get encadenado
db_host = config_sistema.get("database", {}).get("host", "localhost")
Capítulo 2.3: Sets y Tuplas — Cuando Importa la Semántica
# SETS — colecciones sin duplicados, búsqueda O(1)
emails_únicos = {"a@b.com", "c@d.com", "a@b.com"} # Solo guarda una vez
print(emails_únicos) # {"a@b.com", "c@d.com"}
# Operaciones de conjuntos — extremadamente útiles en análisis
usuarios_premium = {"ana", "pedro", "maría", "juan"}
usuarios_activos = {"pedro", "juan", "carlos", "luis"}
# ¿Quiénes son premium Y activos?
ambos = usuarios_premium & usuarios_activos # {"pedro", "juan"}
# ¿Quiénes son premium O activos?
alguno = usuarios_premium | usuarios_activos # {"ana", "pedro", "maría", "juan", "carlos", "luis"}
# ¿Premium pero NO activos?
solo_premium = usuarios_premium - usuarios_activos # {"ana", "maría"}
# ¿En uno pero no en el otro?
diferencia_simetrica = usuarios_premium ^ usuarios_activos # {"ana", "maría", "carlos", "luis"}
# Sets para deduplicar datos sucios
ips_bruteforce = ["192.168.1.1", "192.168.1.1", "10.0.0.1", "192.168.1.1"]
ips_únicas = list(set(ips_bruteforce)) # ["192.168.1.1", "10.0.0.1"]
# TUPLAS — colecciones inmutables, la estructura de datos subestimada
# Úsalas cuando el dato NO debe cambiar
coordenadas = (19.4326, -99.1332) # CDMX — lat, long
punto_x, punto_y = coordenadas # Unpacking
# Named tuples — el mejor de dos mundos (nombre + inmutabilidad)
from collections import namedtuple
from typing import NamedTuple # La forma moderna con type hints
class Punto(NamedTuple):
latitud: float
longitud: float
nombre: str = "Desconocido"
cdmx = Punto(19.4326, -99.1332, "Ciudad de México")
print(cdmx.latitud) # 19.4326
print(cdmx[0]) # 19.4326 — también funciona por índice
lat, lon, nombre = cdmx # Unpacking
# Tuplas como claves de diccionario (los sets y listas NO pueden serlo)
distancias = {
("CDMX", "Guadalajara"): 550, # Kilómetros
("CDMX", "Monterrey"): 880
}
Capítulo 2.4: Comprensiones — Python Idiomático
Las comprensiones son la característica que hace que el código Python sea reconocible al instante. Son más rápidas que los loops equivalentes y, cuando se usan bien, más legibles.
# List comprehension
cuadrados = [x**2 for x in range(10)]
# vs el equivalente en loop:
cuadrados = []
for x in range(10):
cuadrados.append(x**2)
# Con condición (filter)
pares = [x for x in range(20) if x % 2 == 0]
# Transformación + filtro
emails_válidos = [email.lower() for email in emails_raw
if "@" in email and "." in email]
# Dict comprehension — transformar diccionarios
precios = {"mango": 25, "papaya": 18, "guanábana": 35}
con_iva = {fruta: precio * 1.16 for fruta, precio in precios.items()}
# Set comprehension — deduplica automáticamente
dominios = {email.split("@")[1] for email in emails if "@" in email}
# Generator expression — como list comprehension pero lazy (no carga todo en memoria)
# CRUCIAL para procesar archivos grandes o streams de datos
suma_cuadrados = sum(x**2 for x in range(1_000_000)) # No crea lista de 1M elementos
# El límite: no anides más de 2 niveles — pierde legibilidad
# ❌ Difícil de leer:
resultado = [[j for j in range(5)] for i in range(5) if i % 2 == 0]
# ✅ Usa una función nombrada para lógica compleja:
def generar_fila(i):
return [j for j in range(5)]
resultado = [generar_fila(i) for i in range(5) if i % 2 == 0]
PARTE III: Python Intermedio — Donde Todo Cambia
Capítulo 3.1: Programación Orientada a Objetos
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
import uuid
# En 2026, los dataclasses son la forma preferida de crear clases de datos
@dataclass
class Usuario:
"""Representación de un usuario del sistema."""
nombre: str
email: str
id: str = field(default_factory=lambda: str(uuid.uuid7()))
activo: bool = True
creado: datetime = field(default_factory=datetime.now)
roles: list[str] = field(default_factory=list)
def __post_init__(self):
"""Validación automática después de la inicialización."""
if "@" not in self.email:
raise ValueError(f"Email inválido: {self.email}")
self.email = self.email.lower()
@property
def es_admin(self) -> bool:
"""Verifica si el usuario tiene rol de administrador."""
return "admin" in self.roles
def agregar_rol(self, rol: str) -> None:
"""Agrega un rol al usuario si no lo tiene ya."""
if rol not in self.roles:
self.roles.append(rol)
def __repr__(self) -> str:
return f"Usuario(nombre={self.nombre!r}, email={self.email!r})"
# Herencia — cuándo usarla (y cuándo no)
@dataclass
class UsuarioAdmin(Usuario):
"""Usuario con privilegios de administración."""
nivel_acceso: int = 1
def __post_init__(self):
super().__post_init__() # Ejecuta validaciones del padre
self.roles.append("admin") # Los admins siempre tienen el rol
# Composición sobre herencia — el principio moderno
@dataclass
class Sesion:
"""Sesión activa de un usuario."""
usuario: Usuario
ip: str
token: str = field(default_factory=lambda: str(uuid.uuid7()))
iniciada: datetime = field(default_factory=datetime.now)
def es_valida(self, tiempo_max_horas: int = 8) -> bool:
"""Verifica si la sesión sigue siendo válida."""
from datetime import timedelta
expira = self.iniciada + timedelta(hours=tiempo_max_horas)
return datetime.now() < expira and self.usuario.activo
# Uso
usuario = Usuario("Jafet Brito", "jafet@example.com")
usuario.agregar_rol("researcher")
sesion = Sesion(usuario, "192.168.1.100")
print(sesion.es_valida()) # True
Capítulo 3.2: Decoradores — El Patrón que Nadie Explica Bien
Los decoradores son una de las características más potentes de Python y, simultáneamente, la que más confunde a los programadores de otros lenguajes.
import functools
import time
import logging
from typing import Callable, Any
# Entendiendo decoradores desde cero
# Un decorador es simplemente una función que toma una función y retorna una función
def medir_tiempo(func: Callable) -> Callable:
"""Decorador que mide el tiempo de ejecución de una función."""
@functools.wraps(func) # Preserva el nombre y docstring de func
def wrapper(*args, **kwargs) -> Any:
inicio = time.perf_counter()
try:
resultado = func(*args, **kwargs)
return resultado
finally:
fin = time.perf_counter()
print(f"⏱️ {func.__name__} ejecutó en {fin-inicio:.4f}s")
return wrapper
# Decorador con parámetros — el siguiente nivel
def reintentar(max_intentos: int = 3, espera: float = 1.0):
"""Decorador que reintenta una función si falla."""
def decorador(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
ultimo_error = None
for intento in range(max_intentos):
try:
return func(*args, **kwargs)
except Exception as e:
ultimo_error = e
print(f"Intento {intento+1}/{max_intentos} falló: {e}")
if intento < max_intentos - 1:
time.sleep(espera)
raise ultimo_error
return wrapper
return decorador
# Decoradores de clase — para estado persistente
class cache_resultado:
"""Decorador con cache — memoización simple."""
def __init__(self, func: Callable):
self.func = func
self.cache = {}
functools.update_wrapper(self, func)
def __call__(self, *args):
if args not in self.cache:
self.cache[args] = self.func(*args)
return self.cache[args]
# Uso de los decoradores
@medir_tiempo
@reintentar(max_intentos=3, espera=0.5)
def consultar_api_externa(endpoint: str) -> dict:
"""Consulta una API externa con reintentos automáticos."""
import requests
response = requests.get(endpoint, timeout=5)
response.raise_for_status()
return response.json()
@cache_resultado
def fibonacci(n: int) -> int:
"""Fibonacci con memoización — de O(2^n) a O(n)."""
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Python tiene functools.lru_cache y functools.cache para esto:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci_profesional(n: int) -> int:
if n < 2:
return n
return fibonacci_profesional(n-1) + fibonacci_profesional(n-2)
Capítulo 3.3: Generators e Iterators — Memoria Infinita Finita
# El problema: cargar 1 millón de registros en memoria
# ❌ Carga todo en memoria — puede crashear con archivos grandes:
def leer_todos_los_logs(archivo: str) -> list:
with open(archivo) as f:
return f.readlines() # Si el archivo es de 10GB, esto falla
# ✅ Generator — procesa un elemento a la vez:
def leer_logs_stream(archivo: str):
"""Lee logs línea por línea sin cargar todo en memoria."""
with open(archivo) as f:
for linea in f:
yield linea.strip() # yield en lugar de return = generator
# Uso — procesa terabytes de datos con memoria constante
for linea in leer_logs_stream("sistema.log"):
if "ERROR" in linea:
procesar_error(linea)
# Generators con estado complejo
def paginar_api(url: str, limite: int = 100):
"""Generator que pagina automáticamente una API REST."""
import requests
pagina = 1
while True:
respuesta = requests.get(url, params={"page": pagina, "limit": limite})
datos = respuesta.json()
if not datos:
return # Fin de los datos
yield from datos # yield from delega a otro iterable
if len(datos) < limite:
return # Última página
pagina += 1
# Uso:
for usuario in paginar_api("https://api.ejemplo.com/usuarios"):
procesar(usuario) # Sin cargar todos los usuarios en memoria
# Generator expressions — lazy evaluation
# ✅ Eficiente — no carga todo:
suma = sum(x**2 for x in range(1_000_000))
# ❌ Carga todo en memoria:
suma = sum([x**2 for x in range(1_000_000)])
Capítulo 3.4: Context Managers y el Protocolo with
from contextlib import contextmanager, asynccontextmanager
import time
import logging
# El with garantiza cleanup aunque haya excepciones
# No es solo para archivos — es un patrón de gestión de recursos
# Creando tu propio context manager con contextmanager
@contextmanager
def cronometro(nombre: str = "operación"):
"""Mide el tiempo de un bloque de código."""
inicio = time.perf_counter()
try:
yield # El bloque 'with' se ejecuta aquí
finally:
fin = time.perf_counter()
print(f"⏱️ {nombre}: {fin-inicio:.4f}s")
# Uso:
with cronometro("procesamiento de datos"):
datos = procesar_dataset_grande()
# Context manager para manejo de transacciones de DB
@contextmanager
def transaccion_db(conexion):
"""Garantiza commit o rollback en transacciones."""
try:
yield conexion
conexion.commit()
print("✅ Transacción committed")
except Exception as e:
conexion.rollback()
print(f"❌ Rollback por: {e}")
raise
# Context manager como clase (protocolo completo)
class ConexionSegura:
"""Gestiona una conexión con cleanup garantizado."""
def __init__(self, host: str, timeout: int = 30):
self.host = host
self.timeout = timeout
self.conexion = None
def __enter__(self):
print(f"🔌 Conectando a {self.host}...")
self.conexion = crear_conexion(self.host, self.timeout)
return self.conexion
def __exit__(self, exc_type, exc_val, exc_tb):
if self.conexion:
self.conexion.cerrar()
print(f"🔌 Desconectado de {self.host}")
# Retornar True suprime la excepción (raramente deseado)
return False
with ConexionSegura("db.ejemplo.com") as conn:
resultados = conn.query("SELECT * FROM usuarios")
Capítulo 3.5: Módulos, Paquetes y el Arte de Organizar Código
# Imports — el orden importa (PEP 8)
# 1. Imports de la librería estándar
import os
import sys
import json
from pathlib import Path
from typing import Optional
# 2. Imports de third-party
import requests
import numpy as np
import pandas as pd
# 3. Imports locales
from .utils import validar_email
from .models import Usuario
# Imports relativos vs absolutos
# En paquetes, los imports relativos son más robustos:
from . import utils # Importa utils del mismo paquete
from ..models import Usuario # Importa del paquete padre
# El anti-pattern más común — NUNCA hagas esto:
from modulo import * # Contamina el namespace, oculta dependencias
# __all__ — controla qué exporta tu módulo
__all__ = ["FuncionPublica", "ClasePublica"] # Solo esto es público
# Importación lazy — para módulos pesados
def analizar_imagen(ruta: str):
"""Análisis de imagen — importa PIL solo si se necesita."""
from PIL import Image # Import dentro de la función
return Image.open(ruta).size
# pyproject.toml — la configuración moderna del proyecto 2026
# [project]
# name = "mi-proyecto"
# version = "1.0.0"
# requires-python = ">=3.14"
# dependencies = [
# "requests>=2.32.0",
# "pydantic>=2.0.0",
# "anthropic>=0.40.0"
# ]
#
# [project.optional-dependencies]
# dev = ["pytest", "ruff", "mypy"]
# ai = ["langchain", "openai", "anthropic"]
Capítulo 3.6: Python 3.14 — Las Novedades que Importan en 2026
# 1. T-STRINGS (Template Strings) — PEP 750
# El problema de los f-strings: evalúan inmediatamente, sin control
nombre = "<script>alert('XSS')</script>"
# ❌ Peligroso en HTML:
html = f"<div>{nombre}</div>" # Inyección XSS
# ✅ T-strings permiten procesamiento personalizado:
from html import escape
def html_safe(template):
"""Procesa t-string escapando HTML automáticamente."""
parts = []
for part in template:
if isinstance(part, str):
parts.append(part)
else:
parts.append(escape(str(part.value)))
return "".join(parts)
# Sintaxis (Python 3.14):
nombre = "<script>alert('XSS')</script>"
html_seguro = html_safe(t"<div>{nombre}</div>")
# "<div><script>alert('XSS')</script></div>"
# 2. FREE-THREADING — PEP 779
# El GIL (Global Interpreter Lock) ya no es obligatorio
# Verdadero paralelismo en threads para CPU-bound tasks
import threading
def calcular_intensivo(datos):
return sum(x**2 for x in datos)
# Antes del GIL removal: los threads no aceleraban trabajo CPU
# En Python 3.14 con free-threading: verdadero paralelismo
threads = [
threading.Thread(target=calcular_intensivo, args=(chunk,))
for chunk in chunks_de_datos
]
# 3. UUID7 — NATIVO
from uuid import uuid7
id_nuevo = uuid7() # Ordenado por tiempo + aleatorio
# Perfecto para IDs de base de datos (mejor que uuid4 para indexación)
# 4. DATE.STRPTIME — Más intuitivo
from datetime import date
fecha = date.strptime("2026-06-11", "%Y-%m-%d") # Antes: datetime.strptime + .date()
# 5. MATCH STATEMENT mejorado — Pattern matching más potente
def procesar_respuesta_api(respuesta: dict) -> str:
match respuesta:
case {"status": "success", "data": {"user": {"name": nombre}}}:
return f"Bienvenido, {nombre}"
case {"status": "error", "code": 401}:
return "No autorizado"
case {"status": "error", "code": code, "message": msg}:
return f"Error {code}: {msg}"
case _:
return "Respuesta desconocida"
PARTE IV: Python para el Mundo Real
Capítulo 4.1: Archivos, Paths y el Sistema de Archivos
from pathlib import Path # La forma moderna — olvida os.path
# pathlib.Path — orientado a objetos, multiplataforma
directorio = Path.home() / "proyectos" / "mi-app" # / como operador
directorio.mkdir(parents=True, exist_ok=True)
# Operaciones comunes
archivo = directorio / "datos.json"
print(archivo.exists()) # Verifica existencia
print(archivo.suffix) # ".json"
print(archivo.stem) # "datos"
print(archivo.parent) # /home/usuario/proyectos/mi-app
# Leer y escribir
texto = archivo.read_text(encoding="utf-8")
archivo.write_text("contenido", encoding="utf-8")
datos = json.loads(archivo.read_bytes())
archivo.write_bytes(json.dumps(datos, indent=2).encode())
# Glob — búsqueda de archivos con patrones
archivos_python = list(Path(".").rglob("*.py")) # Recursivo
logs_hoy = list(Path("/var/log").glob("*.log")) # Solo nivel actual
# Procesar archivos grandes eficientemente
def contar_ips_en_log(ruta_log: Path) -> dict:
"""Cuenta ocurrencias de IPs en un log de acceso."""
from collections import Counter
ips = Counter()
with ruta_log.open(encoding="utf-8") as f:
for linea in f: # Lee línea por línea — no carga todo
if ip := extraer_ip(linea):
ips[ip] += 1
return dict(ips.most_common(10)) # Top 10 IPs
# CSV con pandas (más sobre pandas en Parte V)
import csv
# Para CSVs simples:
with open("datos.csv", "r", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for fila in reader:
print(fila["nombre"], fila["email"])
# Escribir CSV:
campos = ["nombre", "email", "activo"]
with open("salida.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=campos)
writer.writeheader()
writer.writerows(usuarios)
Capítulo 4.2: APIs REST — Habla con el Mundo
import requests
from dataclasses import dataclass
from typing import Optional
import os
# El patrón profesional para consumir APIs
@dataclass
class ConfigAPI:
base_url: str
api_key: str
timeout: int = 30
class ClienteAPI:
"""Cliente reutilizable para APIs REST con manejo de errores."""
def __init__(self, config: ConfigAPI):
self.config = config
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {config.api_key}",
"Content-Type": "application/json",
"User-Agent": "MiApp/1.0"
})
def get(self, endpoint: str, params: dict = None) -> dict:
"""GET request con manejo de errores."""
url = f"{self.config.base_url}/{endpoint.lstrip('/')}"
try:
response = self.session.get(
url,
params=params,
timeout=self.config.timeout
)
response.raise_for_status() # Lanza HTTPError en 4xx/5xx
return response.json()
except requests.Timeout:
raise TimeoutError(f"API no respondió en {self.config.timeout}s")
except requests.HTTPError as e:
if e.response.status_code == 401:
raise PermissionError("API key inválida o expirada")
elif e.response.status_code == 429:
raise RuntimeError("Rate limit excedido — espera antes de reintentar")
raise
def post(self, endpoint: str, datos: dict) -> dict:
url = f"{self.config.base_url}/{endpoint.lstrip('/')}"
response = self.session.post(url, json=datos, timeout=self.config.timeout)
response.raise_for_status()
return response.json()
# Uso — las credenciales SIEMPRE de variables de entorno
config = ConfigAPI(
base_url="https://api.ejemplo.com/v1",
api_key=os.environ["API_KEY"] # Nunca hardcodear
)
cliente = ClienteAPI(config)
usuarios = cliente.get("/usuarios", params={"activo": True, "limite": 50})
# Rate limiting inteligente con backoff exponencial
import time
from functools import wraps
def con_backoff(max_intentos=5, base_espera=1.0):
"""Decorador con backoff exponencial para rate limiting."""
def decorador(func):
@wraps(func)
def wrapper(*args, **kwargs):
for intento in range(max_intentos):
try:
return func(*args, **kwargs)
except RuntimeError as e:
if "Rate limit" not in str(e):
raise
espera = base_espera * (2 ** intento)
print(f"Rate limited. Esperando {espera}s...")
time.sleep(espera)
raise RuntimeError("Rate limit persistente después de backoff")
return wrapper
return decorador
Capítulo 4.3: Bases de Datos con Python
# SQLite para desarrollo y apps pequeñas — incluido en Python estándar
import sqlite3
from contextlib import contextmanager
from pathlib import Path
# SQLAlchemy para proyectos serios — el ORM estándar de Python
from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime
from sqlalchemy.orm import DeclarativeBase, Session
from datetime import datetime
class Base(DeclarativeBase):
pass
class Usuario(Base):
__tablename__ = "usuarios"
id = Column(Integer, primary_key=True, autoincrement=True)
nombre = Column(String(100), nullable=False)
email = Column(String(255), unique=True, nullable=False)
activo = Column(Boolean, default=True)
creado = Column(DateTime, default=datetime.now)
def __repr__(self):
return f"<Usuario {self.email}>"
# Configuración de base de datos desde variables de entorno
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./dev.db")
engine = create_engine(DATABASE_URL, echo=False)
# Crear tablas
Base.metadata.create_all(engine)
# Operaciones CRUD
def crear_usuario(nombre: str, email: str) -> Usuario:
with Session(engine) as session:
usuario = Usuario(nombre=nombre, email=email)
session.add(usuario)
session.commit()
session.refresh(usuario)
return usuario
def obtener_usuarios_activos() -> list[Usuario]:
with Session(engine) as session:
return session.query(Usuario).filter(Usuario.activo == True).all()
# Para PostgreSQL en producción:
# DATABASE_URL = "postgresql://usuario:password@host:5432/dbname"
# pip install psycopg2-binary
# Para MongoDB:
# pip install pymongo
from pymongo import MongoClient
mongo = MongoClient(os.environ["MONGODB_URL"])
db = mongo["mi_app"]
usuarios_col = db["usuarios"]
# Insertar
resultado = usuarios_col.insert_one({
"nombre": "Jafet",
"email": "jafet@example.com",
"activo": True
})
# Consultar
for usuario in usuarios_col.find({"activo": True}):
print(usuario["nombre"])
Capítulo 4.4: Testing — Código que Confía en Sí Mismo
Principio Zero Trust aplicado al testing: No asumas que tu código funciona. Demuéstralo.
# pytest — el framework estándar de testing en Python 2026
import pytest
from unittest.mock import Mock, patch, MagicMock
# Test básico
def test_calcular_iva():
"""El nombre debe describir qué testea."""
resultado = calcular_iva(100.0, 0.16)
assert resultado == 116.0, f"Esperaba 116.0, obtuve {resultado}"
# Parametrize — testea múltiples casos sin duplicar código
@pytest.mark.parametrize("precio,tasa,esperado", [
(100.0, 0.16, 116.0),
(0.0, 0.16, 0.0),
(50.0, 0.10, 55.0),
(100.0, 0.0, 100.0),
])
def test_calcular_iva_parametrizado(precio, tasa, esperado):
assert calcular_iva(precio, tasa) == pytest.approx(esperado)
# Fixtures — setup reutilizable para tests
@pytest.fixture
def usuario_prueba():
"""Crea un usuario de prueba para los tests."""
return Usuario(
nombre="Usuario Test",
email="test@example.com"
)
@pytest.fixture
def cliente_api_mock():
"""Mock del cliente de API para tests sin llamadas reales."""
mock = Mock(spec=ClienteAPI)
mock.get.return_value = {"status": "success", "data": []}
return mock
def test_procesar_usuario(usuario_prueba):
resultado = procesar_usuario(usuario_prueba)
assert resultado["procesado"] == True
assert resultado["email"] == "test@example.com"
# Mocking — testea código con dependencias externas
def test_consultar_api_con_mock(cliente_api_mock):
"""Testea sin hacer llamadas reales a la API."""
servicio = ServicioUsuarios(cliente_api_mock)
usuarios = servicio.obtener_todos()
cliente_api_mock.get.assert_called_once_with("/usuarios")
assert isinstance(usuarios, list)
# Testing de excepciones
def test_email_invalido_lanza_error():
with pytest.raises(ValueError, match="Email inválido"):
Usuario(nombre="Test", email="no-es-un-email")
# Coverage — mide qué porcentaje de tu código tienen tests
# pytest --cov=src --cov-report=html
# Meta: 80%+ en código de producción
Capítulo 4.5: Concurrencia y Paralelismo Moderno
import asyncio
import aiohttp
import concurrent.futures
from typing import Coroutine
# ASYNCIO — para I/O concurrente (APIs, bases de datos, archivos)
# La regla: usa async/await cuando esperas operaciones externas
async def obtener_usuario(session: aiohttp.ClientSession, user_id: int) -> dict:
"""Obtiene un usuario de forma asíncrona."""
async with session.get(f"https://api.ejemplo.com/usuarios/{user_id}") as response:
return await response.json()
async def obtener_muchos_usuarios(ids: list[int]) -> list[dict]:
"""Obtiene múltiples usuarios concurrentemente."""
async with aiohttp.ClientSession() as session:
# Lanza todas las peticiones concurrentemente
tareas = [obtener_usuario(session, id) for id in ids]
resultados = await asyncio.gather(*tareas, return_exceptions=True)
# Filtra errores
return [r for r in resultados if not isinstance(r, Exception)]
# Ejecutar código async desde código sync
usuarios = asyncio.run(obtener_muchos_usuarios([1, 2, 3, 4, 5]))
# Obtiene 5 usuarios en el tiempo de 1 petición secuencial
# ThreadPoolExecutor — para código bloqueante en paralelo
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
def consultar_ip(ip: str) -> dict:
"""Consulta información de una IP (operación bloqueante)."""
response = requests.get(f"https://ipapi.co/{ip}/json/", timeout=5)
return {"ip": ip, "data": response.json()}
ips = ["8.8.8.8", "1.1.1.1", "208.67.222.222", "9.9.9.9"]
with ThreadPoolExecutor(max_workers=4) as executor:
futuros = {executor.submit(consultar_ip, ip): ip for ip in ips}
for futuro in as_completed(futuros):
ip = futuros[futuro]
try:
resultado = futuro.result()
print(f"{ip}: {resultado['data'].get('country_name')}")
except Exception as e:
print(f"{ip}: Error — {e}")
# ProcessPoolExecutor — para CPU-bound tasks (Python 3.14 free-threading)
from concurrent.futures import ProcessPoolExecutor
def analizar_archivo_log(ruta: str) -> dict:
"""Análisis intensivo de un archivo de log."""
# ... procesamiento CPU-intensivo
return resultados
archivos = list(Path("/var/log").glob("*.log"))
with ProcessPoolExecutor() as executor:
resultados = list(executor.map(analizar_archivo_log, archivos))
PARTE V: Python + IA — El Diferenciador de 2026
Capítulo 5.1: El Ecosistema de IA en Python
En 2026, Python es literalmente el lenguaje nativo de la inteligencia artificial. Entender su ecosistema es entender el futuro del software.
EL MAPA DEL ECOSISTEMA AI/PYTHON 2026:
FUNDAMENTOS MATEMÁTICOS:
├── NumPy — operaciones de arrays y álgebra lineal
├── SciPy — computación científica
└── SymPy — matemáticas simbólicas
DATA MANIPULATION:
├── Pandas — el Excel programable
├── Polars — pandas en Rust, 10-100x más rápido para datasets grandes
└── DuckDB — SQL analítico en Python, sin servidor
VISUALIZACIÓN:
├── Matplotlib — el estándar, máximo control
├── Seaborn — estadístico, sobre matplotlib
├── Plotly — interactivo, web-ready
└── Altair — declarativo, muy elegante
MACHINE LEARNING:
├── scikit-learn — ML clásico, referencia universal
├── XGBoost/LightGBM/CatBoost — gradient boosting (Kaggle kings)
└── PyCaret — AutoML accesible
DEEP LEARNING:
├── PyTorch — el estándar de investigación 2026
├── TensorFlow/Keras — Google, producción enterprise
└── JAX — diferenciación automática, investigación avanzada
LLMs E IA GENERATIVA:
├── anthropic — SDK oficial de Claude
├── openai — SDK de GPT
├── LangChain — orquestación de LLMs
├── LlamaIndex — RAG (Retrieval Augmented Generation)
├── instructor — structured output con LLMs
└── PydanticAI — agentes con validación
HERRAMIENTAS:
├── Jupyter — notebooks interactivos
├── DVC — versionado de datos y modelos
└── MLflow — tracking de experimentos
Capítulo 5.2: APIs de LLMs desde Python — Claude, GPT, Gemini
import os
import anthropic
from anthropic import Anthropic
# ========================================
# CLAUDE (ANTHROPIC)
# ========================================
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
# Llamada básica
def preguntar_claude(pregunta: str, sistema: str = None) -> str:
"""Consulta a Claude con manejo de errores."""
kwargs = {
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [{"role": "user", "content": pregunta}]
}
if sistema:
kwargs["system"] = sistema
mensaje = client.messages.create(**kwargs)
return mensaje.content[0].text
# Streaming — para respuestas largas en tiempo real
def claude_stream(prompt: str):
"""Stream de respuesta de Claude token por token."""
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=2048,
messages=[{"role": "user", "content": prompt}]
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
print() # Nueva línea al final
# Prompt caching — 90% descuento en tokens de system prompt
def analizar_con_contexto_grande(documento: str, pregunta: str) -> str:
"""Analiza un documento con prompt caching activado."""
return client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[{
"type": "text",
"text": f"Eres un analizador de documentos. Documento:\n\n{documento}",
"cache_control": {"type": "ephemeral"} # Cachear el doc grande
}],
messages=[{"role": "user", "content": pregunta}]
).content[0].text
# Manejo de conversaciones multi-turno
class ConversacionClaude:
"""Mantiene el historial de una conversación con Claude."""
def __init__(self, sistema: str = ""):
self.client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
self.historial = []
self.sistema = sistema
def decir(self, mensaje: str) -> str:
"""Envía un mensaje y obtiene respuesta."""
self.historial.append({"role": "user", "content": mensaje})
respuesta = self.client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
system=self.sistema,
messages=self.historial
)
texto = respuesta.content[0].text
self.historial.append({"role": "assistant", "content": texto})
return texto
# Outputs estructurados con instructor
import instructor
from pydantic import BaseModel, Field
claude_instructor = instructor.from_anthropic(
Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
)
class AnalisisSentimiento(BaseModel):
sentimiento: str = Field(description="positivo, negativo, o neutro")
confianza: float = Field(ge=0, le=1, description="0-1")
palabras_clave: list[str]
resumen: str
def analizar_sentimiento(texto: str) -> AnalisisSentimiento:
return claude_instructor.messages.create(
model="claude-sonnet-4-6",
max_tokens=500,
response_model=AnalisisSentimiento,
messages=[{
"role": "user",
"content": f"Analiza el sentimiento de: '{texto}'"
}]
)
resultado = analizar_sentimiento("Me encanta Python, es increíblemente expresivo")
print(f"Sentimiento: {resultado.sentimiento} ({resultado.confianza:.0%})")
Capítulo 5.3: LangChain y Agentes de IA
from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_community.tools import DuckDuckGoSearchRun
from langchain.agents import create_react_agent, AgentExecutor
# LCEL — LangChain Expression Language (la forma moderna)
modelo = ChatAnthropic(
model="claude-sonnet-4-6",
api_key=os.environ["ANTHROPIC_API_KEY"]
)
prompt = ChatPromptTemplate.from_messages([
("system", "Eres un experto en ciberseguridad. Responde en español."),
("user", "{pregunta}")
])
# Cadena simple con pipe |
cadena = prompt | modelo | StrOutputParser()
respuesta = cadena.invoke({"pregunta": "¿Qué es un SQL injection?"})
# RAG — Retrieval Augmented Generation
from langchain_community.vectorstores import Chroma
from langchain_anthropic import AnthropicEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader
def crear_rag_desde_pdf(ruta_pdf: str):
"""Crea un sistema RAG que responde preguntas sobre un PDF."""
# 1. Carga el documento
loader = PyPDFLoader(ruta_pdf)
documentos = loader.load()
# 2. Divide en chunks
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
chunks = splitter.split_documents(documentos)
# 3. Crea embeddings y vector store
embeddings = AnthropicEmbeddings()
vectorstore = Chroma.from_documents(chunks, embeddings)
# 4. Crea el retriever
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# 5. Cadena RAG
template = """Responde basándote SOLO en el siguiente contexto:
{contexto}
Pregunta: {pregunta}
Si no puedes responder con el contexto dado, dilo explícitamente."""
prompt = ChatPromptTemplate.from_template(template)
from langchain_core.runnables import RunnablePassthrough
cadena_rag = (
{"contexto": retriever, "pregunta": RunnablePassthrough()}
| prompt
| modelo
| StrOutputParser()
)
return cadena_rag
# Agente con herramientas
def crear_agente_investigacion():
"""Agente que puede buscar en internet para responder preguntas."""
herramientas = [DuckDuckGoSearchRun()]
prompt_agente = ChatPromptTemplate.from_messages([
("system", """Eres un investigador experto en ciberseguridad.
Tienes acceso a búsqueda web para encontrar información actualizada.
Responde siempre en español con fuentes verificadas."""),
("user", "{input}"),
("placeholder", "{agent_scratchpad}")
])
agente = create_react_agent(modelo, herramientas, prompt_agente)
executor = AgentExecutor(agente=agente, herramientas=herramientas, verbose=True)
return executor
agente = crear_agente_investigacion()
resultado = agente.invoke({
"input": "¿Cuáles son las vulnerabilidades más críticas de 2026 según OWASP?"
})
Capítulo 5.4: Data Science Esencial
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# NUMPY — operaciones vectorizadas (mucho más rápido que loops)
datos = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# Estadísticas básicas
print(f"Media: {datos.mean():.2f}")
print(f"Mediana: {np.median(datos):.2f}")
print(f"Desviación estándar: {datos.std():.2f}")
# Operaciones vectorizadas — miles de veces más rápido que loops
x = np.random.randn(1_000_000) # 1 millón de datos
y = x ** 2 + 2 * x + 1 # Aplica a todo el array de una vez
# PANDAS — análisis de datos estructurados
df = pd.read_csv("ventas_2026.csv", parse_dates=["fecha"])
# Exploración inicial — siempre empieza con esto
print(df.head()) # Primeras 5 filas
print(df.info()) # Tipos y valores nulos
print(df.describe()) # Estadísticas por columna
print(df.isnull().sum()) # Cantidad de valores nulos por columna
# Limpieza de datos
df_limpio = (
df
.dropna(subset=["email"]) # Elimina filas sin email
.drop_duplicates(subset=["id"]) # Elimina duplicados
.assign(
email=lambda x: x["email"].str.lower(), # Normaliza email
fecha=lambda x: pd.to_datetime(x["fecha"]), # Convierte fecha
monto=lambda x: pd.to_numeric(x["monto"], errors="coerce") # Limpia montos
)
.query("monto > 0") # Filtra montos positivos
)
# Análisis por grupo
ventas_por_region = (
df_limpio
.groupby("region")
.agg(
total_ventas=("monto", "sum"),
ticket_promedio=("monto", "mean"),
num_transacciones=("id", "count")
)
.sort_values("total_ventas", ascending=False)
.reset_index()
)
# Visualización profesional
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# Barras de ventas por región
axes[0].bar(ventas_por_region["region"], ventas_por_region["total_ventas"])
axes[0].set_title("Ventas por Región")
axes[0].set_xlabel("Región")
axes[0].set_ylabel("Ventas totales ($)")
plt.setp(axes[0].xaxis.get_majorticklabels(), rotation=45)
# Distribución del ticket promedio
axes[1].hist(df_limpio["monto"], bins=50, edgecolor="black")
axes[1].set_title("Distribución del Monto de Venta")
axes[1].set_xlabel("Monto ($)")
axes[1].set_ylabel("Frecuencia")
plt.tight_layout()
plt.savefig("analisis_ventas.png", dpi=150, bbox_inches="tight")
Capítulo 5.5: Machine Learning con scikit-learn
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.cluster import KMeans
from sklearn.metrics import classification_report, confusion_matrix
import pandas as pd
import numpy as np
# Pipeline profesional de ML
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
# Preparación de datos
df = pd.read_csv("clientes.csv")
# Features y target
X = df.drop("churn", axis=1)
y = df["churn"]
# Split estratificado
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Pipeline que previene data leakage
numericas = ["edad", "ingreso", "meses_cliente", "num_productos"]
categoricas = ["region", "tipo_cuenta", "canal_adquisicion"]
preprocesador = ColumnTransformer([
("num", StandardScaler(), numericas),
("cat", OneHotEncoder(drop="first", handle_unknown="ignore"), categoricas)
])
pipeline = Pipeline([
("preprocesador", preprocesador),
("modelo", RandomForestClassifier(n_estimators=100, random_state=42))
])
# Entrenamiento
pipeline.fit(X_train, y_train)
# Evaluación
y_pred = pipeline.predict(X_test)
print(classification_report(y_test, y_pred, target_names=["No Churn", "Churn"]))
# Cross-validation — evaluación más robusta
scores = cross_val_score(pipeline, X, y, cv=5, scoring="roc_auc")
print(f"ROC-AUC: {scores.mean():.3f} ± {scores.std():.3f}")
# K-Means Clustering — segmentación de clientes
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
# Features para segmentación RFM
rfm = df.groupby("cliente_id").agg(
recencia=("fecha_compra", lambda x: (pd.Timestamp.now() - x.max()).days),
frecuencia=("id_pedido", "count"),
monto=("total", "sum")
).reset_index()
scaler = StandardScaler()
rfm_scaled = scaler.fit_transform(rfm[["recencia", "frecuencia", "monto"]])
# Método del codo para elegir K
inercias = []
for k in range(2, 11):
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(rfm_scaled)
inercias.append(kmeans.inertia_)
# Entrenar con K óptimo
kmeans_final = KMeans(n_clusters=4, random_state=42, n_init=10)
rfm["segmento"] = kmeans_final.fit_predict(rfm_scaled)
# Interpretar segmentos
descripcion_segmentos = rfm.groupby("segmento").agg({
"recencia": "mean",
"frecuencia": "mean",
"monto": "mean"
}).round(2)
print(descripcion_segmentos)
PARTE VI: Python para Ciberseguridad y OSINT
Capítulo 6.1: Python para Seguridad — Mentalidad y Ética
Principio fundamental: Las mismas herramientas que un atacante usa para vulnerar sistemas son las que un defensor usa para protegerlos. La diferencia es la autorización y la intención.
Todo el código de seguridad en este capítulo es para:
- ✅ Pruebas en tus propios sistemas
- ✅ Entornos de laboratorio autorizados (TryHackMe, HackTheBox, tu homelab)
- ✅ Contratos de pentesting con alcance definido y firmado
- ❌ Nunca en sistemas sin autorización explícita
# Conceptos fundamentales de seguridad en Python
# 1. Manejo seguro de secretos
import os
from functools import lru_cache
# ❌ NUNCA hardcodear credenciales:
api_key = "sk-proj-abc123..." # ESTO DESTRUYE TU CARRERA
# ✅ Siempre de variables de entorno:
api_key = os.environ["OPENAI_API_KEY"] # KeyError si no existe = bueno
api_key = os.getenv("OPENAI_API_KEY", "") # String vacío si no existe = cuidado
# Para desarrollo local, usa python-dotenv:
from dotenv import load_dotenv
load_dotenv() # Carga el .env automáticamente
# 2. Hashing de contraseñas — nunca en texto plano
import hashlib
import secrets
import bcrypt # pip install bcrypt
def hash_password(password: str) -> str:
"""Hash seguro de contraseña con bcrypt."""
salt = bcrypt.gensalt(rounds=12)
return bcrypt.hashpw(password.encode(), salt).decode()
def verificar_password(password: str, hash_guardado: str) -> bool:
"""Verifica una contraseña contra su hash."""
return bcrypt.checkpw(password.encode(), hash_guardado.encode())
# 3. Generación de tokens seguros
token_sesion = secrets.token_hex(32) # 64 caracteres hex
token_url = secrets.token_urlsafe(32) # URL-safe base64
numero_random = secrets.randbelow(1_000_000) # Más seguro que random
# 4. Sanitización de inputs
import re
import html
def sanitizar_input(texto: str, max_longitud: int = 1000) -> str:
"""Sanitiza input del usuario para prevenir inyecciones."""
# Limitar longitud
texto = texto[:max_longitud]
# Escapar HTML
texto = html.escape(texto)
# Remover caracteres de control
texto = re.sub(r'[\x00-\x1f\x7f]', '', texto)
return texto.strip()
Capítulo 6.2: Networking y Análisis de Paquetes
import socket
import nmap # pip install python-nmap
import scapy.all as scapy # pip install scapy
import ipaddress
# Escaneo de puertos con nmap (requiere nmap instalado en el sistema)
def escanear_objetivo(objetivo: str, puertos: str = "1-1000") -> dict:
"""
Escanea puertos de un objetivo.
SOLO para sistemas autorizados.
"""
nm = nmap.PortScanner()
nm.scan(objetivo, puertos, arguments="-sS -T4")
resultados = {}
for host in nm.all_hosts():
resultados[host] = {
"estado": nm[host].state(),
"puertos_abiertos": []
}
for protocolo in nm[host].all_protocols():
for puerto in nm[host][protocolo].keys():
if nm[host][protocolo][puerto]["state"] == "open":
resultados[host]["puertos_abiertos"].append({
"puerto": puerto,
"protocolo": protocolo,
"servicio": nm[host][protocolo][puerto]["name"]
})
return resultados
# Análisis de tráfico con Scapy (requiere privilegios root/admin)
def capturar_dns_queries(interfaz: str = "eth0", duracion: int = 60) -> list:
"""
Captura y analiza queries DNS.
Útil para detectar exfiltración de datos por DNS.
"""
queries = []
def analizar_paquete(paquete):
if paquete.haslayer(scapy.DNS) and paquete.getlayer(scapy.DNS).qr == 0:
query = {
"timestamp": float(paquete.time),
"origen": paquete[scapy.IP].src if paquete.haslayer(scapy.IP) else "N/A",
"dominio": paquete[scapy.DNS].qd.qname.decode() if paquete[scapy.DNS].qd else "N/A"
}
queries.append(query)
scapy.sniff(
iface=interfaz,
filter="udp port 53",
prn=analizar_paquete,
timeout=duracion
)
return queries
# Análisis de PCAP existente (sin captura en vivo)
def analizar_pcap(ruta_pcap: str) -> dict:
"""Analiza un archivo PCAP y extrae estadísticas."""
paquetes = scapy.rdpcap(ruta_pcap)
estadisticas = {
"total_paquetes": len(paquetes),
"ips_origen": set(),
"ips_destino": set(),
"protocolos": {},
}
for paquete in paquetes:
if paquete.haslayer(scapy.IP):
estadisticas["ips_origen"].add(paquete[scapy.IP].src)
estadisticas["ips_destino"].add(paquete[scapy.IP].dst)
protocolo = paquete.lastlayer().name
estadisticas["protocolos"][protocolo] = \
estadisticas["protocolos"].get(protocolo, 0) + 1
estadisticas["ips_origen"] = list(estadisticas["ips_origen"])
estadisticas["ips_destino"] = list(estadisticas["ips_destino"])
return estadisticas
Capítulo 6.3: OSINT Automatizado con Python
import requests
import json
import time
from typing import Optional
# Framework OSINT modular con Python
class InvestigadorOSINT:
"""
Framework de investigación OSINT.
Solo para investigación autorizada, periodismo, y defensa.
"""
def __init__(self, api_keys: dict):
self.api_keys = api_keys
self.session = requests.Session()
self.session.headers["User-Agent"] = "OSINT-Researcher/1.0"
def consultar_shodan(self, ip: str) -> dict:
"""Obtiene información de Shodan sobre una IP."""
url = f"https://api.shodan.io/shodan/host/{ip}"
params = {"key": self.api_keys["shodan"]}
response = self.session.get(url, params=params, timeout=10)
if response.status_code == 404:
return {"error": "IP no encontrada en Shodan"}
response.raise_for_status()
return response.json()
def consultar_virustotal(self, indicador: str, tipo: str = "ip") -> dict:
"""Consulta VirusTotal para IPs, dominios, o hashes."""
endpoints = {
"ip": f"https://www.virustotal.com/api/v3/ip_addresses/{indicador}",
"domain": f"https://www.virustotal.com/api/v3/domains/{indicador}",
"hash": f"https://www.virustotal.com/api/v3/files/{indicador}"
}
headers = {"x-apikey": self.api_keys["virustotal"]}
response = self.session.get(endpoints[tipo], headers=headers, timeout=10)
response.raise_for_status()
datos = response.json()
stats = datos.get("data", {}).get("attributes", {}).get("last_analysis_stats", {})
return {
"malicioso": stats.get("malicious", 0),
"sospechoso": stats.get("suspicious", 0),
"inofensivo": stats.get("harmless", 0),
"reputacion": datos.get("data", {}).get("attributes", {}).get("reputation", 0)
}
def obtener_historial_dns(self, dominio: str) -> dict:
"""Obtiene historial DNS de SecurityTrails."""
headers = {"APIKEY": self.api_keys["securitytrails"]}
url = f"https://api.securitytrails.com/v1/history/{dominio}/dns/a"
response = self.session.get(url, headers=headers, timeout=10)
response.raise_for_status()
return response.json()
def enriquecer_ip(self, ip: str) -> dict:
"""Enriquecimiento completo de una IP desde múltiples fuentes."""
resultado = {"ip": ip, "fuentes": {}}
# Shodan
try:
resultado["fuentes"]["shodan"] = self.consultar_shodan(ip)
time.sleep(1) # Rate limiting
except Exception as e:
resultado["fuentes"]["shodan"] = {"error": str(e)}
# VirusTotal
try:
resultado["fuentes"]["virustotal"] = self.consultar_virustotal(ip, "ip")
time.sleep(0.5)
except Exception as e:
resultado["fuentes"]["virustotal"] = {"error": str(e)}
# Geolocalización (API pública)
try:
geo = self.session.get(
f"https://ipapi.co/{ip}/json/",
timeout=5
).json()
resultado["fuentes"]["geo"] = {
"pais": geo.get("country_name"),
"ciudad": geo.get("city"),
"isp": geo.get("org"),
"asn": geo.get("asn")
}
except Exception as e:
resultado["fuentes"]["geo"] = {"error": str(e)}
return resultado
# Uso
investigador = InvestigadorOSINT({
"shodan": os.environ["SHODAN_API_KEY"],
"virustotal": os.environ["VIRUSTOTAL_API_KEY"],
"securitytrails": os.environ["SECURITYTRAILS_API_KEY"]
})
perfil_ip = investigador.enriquecer_ip("8.8.8.8")
print(json.dumps(perfil_ip, indent=2))
Capítulo 6.4: Python para Blue Team — Detección y Análisis
import re
import json
from pathlib import Path
from datetime import datetime
from collections import Counter, defaultdict
# Analizador de logs con detección de anomalías
class AnalizadorLogs:
"""
Analiza logs del sistema para detectar actividad sospechosa.
"""
# Patrones de amenazas comunes
PATRONES = {
"brute_force": re.compile(r"Failed password for .+ from (\d+\.\d+\.\d+\.\d+)"),
"sql_injection": re.compile(r"(UNION|SELECT|DROP|INSERT|DELETE|UPDATE|WHERE).*(\bOR\b|\bAND\b)", re.IGNORECASE),
"xss": re.compile(r"<script|javascript:|onerror=|onload=", re.IGNORECASE),
"path_traversal": re.compile(r"\.\./|\.\.\\|%2e%2e", re.IGNORECASE),
"command_injection": re.compile(r"[;&|`$]\s*(ls|cat|whoami|id|uname|wget|curl|bash|sh)\b", re.IGNORECASE)
}
def __init__(self):
self.alertas = []
self.estadisticas = defaultdict(Counter)
def analizar_linea(self, linea: str, numero: int = 0) -> list:
"""Analiza una línea de log en busca de amenazas."""
alertas_encontradas = []
for tipo_amenaza, patron in self.PATRONES.items():
if match := patron.search(linea):
alerta = {
"tipo": tipo_amenaza,
"linea_numero": numero,
"linea": linea.strip(),
"match": match.group(0),
"timestamp": datetime.now().isoformat(),
"severidad": self._calcular_severidad(tipo_amenaza)
}
alertas_encontradas.append(alerta)
self.estadisticas["tipo"][tipo_amenaza] += 1
return alertas_encontradas
def analizar_archivo(self, ruta: str) -> dict:
"""Analiza un archivo de log completo."""
ruta_path = Path(ruta)
if not ruta_path.exists():
raise FileNotFoundError(f"Log no encontrado: {ruta}")
todas_alertas = []
with ruta_path.open(encoding="utf-8", errors="replace") as f:
for numero, linea in enumerate(f, 1):
alertas = self.analizar_linea(linea, numero)
todas_alertas.extend(alertas)
return {
"archivo": str(ruta_path),
"total_lineas": numero,
"total_alertas": len(todas_alertas),
"alertas_por_tipo": dict(self.estadisticas["tipo"]),
"alertas_criticas": [a for a in todas_alertas if a["severidad"] == "ALTA"],
"todas_alertas": todas_alertas
}
def _calcular_severidad(self, tipo: str) -> str:
severidades = {
"command_injection": "ALTA",
"sql_injection": "ALTA",
"brute_force": "MEDIA",
"path_traversal": "MEDIA",
"xss": "MEDIA"
}
return severidades.get(tipo, "BAJA")
def detectar_brute_force(self, ruta_auth_log: str, umbral: int = 5) -> list:
"""Detecta ataques de fuerza bruta en auth.log."""
intentos_por_ip = Counter()
patron_fallo = re.compile(r"Failed password.*from (\d+\.\d+\.\d+\.\d+)")
with open(ruta_auth_log, encoding="utf-8", errors="replace") as f:
for linea in f:
if match := patron_fallo.search(linea):
intentos_por_ip[match.group(1)] += 1
atacantes = [
{"ip": ip, "intentos": count, "severidad": "ALTA" if count > 20 else "MEDIA"}
for ip, count in intentos_por_ip.items()
if count >= umbral
]
return sorted(atacantes, key=lambda x: x["intentos"], reverse=True)
# Exportar alertas a formato SIEM
def exportar_siem(alertas: list, formato: str = "json") -> str:
"""Exporta alertas en formato compatible con SIEMs."""
if formato == "json":
return json.dumps(alertas, indent=2, ensure_ascii=False)
elif formato == "sigma":
# Formato Sigma para reglas de detección
return generar_regla_sigma(alertas)
# Uso
analizador = AnalizadorLogs()
resultados = analizador.analizar_archivo("/var/log/apache2/access.log")
atacantes = analizador.detectar_brute_force("/var/log/auth.log", umbral=10)
print(f"Alertas encontradas: {resultados['total_alertas']}")
print(f"IPs atacantes detectadas: {len(atacantes)}")
for atacante in atacantes[:5]:
print(f" {atacante['ip']}: {atacante['intentos']} intentos")
PARTE VII: Python en Producción
Capítulo 7.1: Entornos Virtuales y Gestión de Dependencias
# uv — el nuevo estándar para gestión de proyectos Python 2026
# 100x más rápido que pip, reemplaza pip + venv + pip-tools
# Instalar uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Crear nuevo proyecto
uv init mi-api
cd mi-api
# Añadir dependencias
uv add fastapi uvicorn anthropic pydantic
uv add --dev pytest ruff mypy # Solo para desarrollo
# Crear entorno virtual y ejecutar
uv run python main.py
uv run pytest
# Sincronizar dependencias del equipo
uv sync # Instala exactamente lo que está en uv.lock
# pyproject.toml generado por uv:
# [project]
# name = "mi-api"
# version = "0.1.0"
# requires-python = ">=3.14"
# dependencies = [
# "fastapi>=0.115.0",
# "anthropic>=0.40.0",
# ]
Capítulo 7.2: Docker + Python
# Dockerfile optimizado para Python 2026
# Multi-stage build — imagen final sin dependencias de compilación
# Etapa 1: Build
FROM python:3.14-slim AS builder
# Instala uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
# Instala dependencias (sin el proyecto)
RUN uv sync --frozen --no-install-project
# Etapa 2: Runtime
FROM python:3.14-slim AS runtime
# Usuario no-root — principio de mínimo privilegio
RUN useradd --system --no-create-home appuser
WORKDIR /app
# Copia el entorno virtual del builder
COPY --from=builder /app/.venv .venv
# Copia el código de la aplicación
COPY src/ src/
# Cambia al usuario no-root
USER appuser
# Variables de entorno
ENV PYTHONPATH=/app/src
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD python -c "import requests; requests.get('http://localhost:8000/health')"
CMD ["/app/.venv/bin/uvicorn", "mi_app.main:app", "--host", "0.0.0.0", "--port", "8000"]
# docker-compose.yml para desarrollo
version: '3.9'
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/mi_app
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} # Del .env local
volumes:
- ./src:/app/src # Hot reload en desarrollo
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: password
POSTGRES_DB: mi_app
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Capítulo 7.3: FastAPI — APIs Modernas en Python
from fastapi import FastAPI, HTTPException, Depends, status
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, EmailStr, Field
from typing import Annotated
import os
app = FastAPI(
title="Mi API",
description="API construida con FastAPI y Python 3.14",
version="1.0.0"
)
# CORS — configuración segura
app.add_middleware(
CORSMiddleware,
allow_origins=["https://mi-frontend.com"], # No usar ["*"] en producción
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
# Modelos Pydantic — validación automática
class CrearUsuario(BaseModel):
nombre: str = Field(min_length=1, max_length=100)
email: EmailStr
password: str = Field(min_length=8)
class UsuarioResponse(BaseModel):
id: int
nombre: str
email: str
activo: bool
model_config = {"from_attributes": True} # Para SQLAlchemy
# Dependency injection — el patrón central de FastAPI
def obtener_db():
"""Inyecta una sesión de base de datos."""
with Session(engine) as session:
yield session
def usuario_actual(token: str = Depends(oauth2_scheme)):
"""Verifica el token y retorna el usuario autenticado."""
payload = verificar_jwt(token)
usuario = obtener_usuario_por_id(payload["sub"])
if not usuario:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token inválido"
)
return usuario
# Endpoints
@app.post("/usuarios", response_model=UsuarioResponse, status_code=201)
async def crear_usuario(
datos: CrearUsuario,
db: Annotated[Session, Depends(obtener_db)]
):
"""Crea un nuevo usuario con validación automática."""
# Verificar email único
if db.query(Usuario).filter(Usuario.email == datos.email).first():
raise HTTPException(
status_code=409,
detail="Email ya registrado"
)
usuario = Usuario(
nombre=datos.nombre,
email=datos.email,
password_hash=hash_password(datos.password)
)
db.add(usuario)
db.commit()
db.refresh(usuario)
return usuario
@app.get("/usuarios/yo", response_model=UsuarioResponse)
async def mi_perfil(
usuario: Annotated[Usuario, Depends(usuario_actual)]
):
"""Retorna el perfil del usuario autenticado."""
return usuario
@app.get("/health")
async def health_check():
"""Endpoint de salud para Docker/Kubernetes."""
return {"status": "healthy", "version": "1.0.0"}
# Para ejecutar:
# uvicorn mi_app.main:app --reload --host 0.0.0.0 --port 8000
Capítulo 7.4: El Portafolio que Consigue Empleo
Tu portafolio en Python 2026 debe demostrar tres cosas: que resuelves problemas reales, que escribes código mantenible, y que usas las herramientas del ecosistema moderno.
Los 5 Proyectos que Abren Puertas
Proyecto 1 — Para cualquier rol: CLI Tool de OSINT personal
# Una herramienta CLI que hace algo útil:
# - Enriquece una IP desde múltiples fuentes
# - Genera un reporte en JSON/HTML
# Tecnologías: typer, rich, httpx, pydantic
Proyecto 2 — Para Data Science / ML:
# Pipeline de ML completo con:
# - EDA automatizado con pandas-profiling
# - Múltiples modelos comparados
# - API FastAPI que sirve predicciones
# - Dashboard con Streamlit o Plotly Dash
Proyecto 3 — Para desarrollo web:
# API REST con:
# - FastAPI + SQLAlchemy + PostgreSQL
# - JWT authentication
# - Tests con pytest (>80% coverage)
# - Docker + docker-compose
# - CI/CD con GitHub Actions
Proyecto 4 — Para IA / LLMs:
# Asistente inteligente que:
# - Conecta con la API de Claude o GPT
# - Tiene memoria de conversación
# - Puede analizar documentos (RAG)
# - Tiene interfaz web con Streamlit o Gradio
Proyecto 5 — Para seguridad:
# Dashboard de seguridad que:
# - Analiza logs de Apache/Nginx
# - Detecta anomalías automáticamente
# - Genera alertas y reportes
# - Visualiza en tiempo real con FastAPI + WebSockets
La Checklist del Proyecto Profesional
✅ README.md con:
- Descripción clara del problema que resuelve
- Demo GIF o screenshot
- Instrucciones de instalación (con Docker)
- Casos de uso concretos
✅ Código con:
- Type hints en todas las funciones
- Docstrings descriptivos
- Tests (pytest)
- Manejo de errores apropiado
- .env.example (sin credenciales reales)
✅ DevOps:
- Dockerfile optimizado
- GitHub Actions para CI
- pyproject.toml moderno (con uv)
- Ruff para linting (no pylint/flake8)
✅ Seguridad:
- Sin credenciales hardcodeadas (verificar con gitleaks)
- Dependencias sin vulnerabilidades conocidas (safety check)
- Input validation con Pydantic
🏁 EPÍLOGO: El Programador Aumentado
Llegaste al final de este libro. Cubrimos desde print("Hola, mundo") hasta agentes de IA autónomos, desde variables básicas hasta arquitecturas de microservicios, desde loops simples hasta análisis de tráfico de red.
Pero quiero dejarte con algo que ningún libro de programación suele decir:
Lo que Python en 2026 Realmente Significa
Python no es solo un lenguaje. Es el idioma común entre los humanos y la nueva capa de inteligencia que estamos construyendo sobre el mundo. Cuando escribes código Python que integra un LLM, no estás escribiendo un programa — estás definiendo el comportamiento de un sistema inteligente.
Con ese poder viene responsabilidad:
- 🔐 El código inseguro no es solo un bug — es una vulnerabilidad que alguien puede explotar
- 🤖 El código de IA mal diseñado no es solo ineficiente — puede amplificar sesgos y tomar decisiones que afectan vidas reales
- 🌐 El código sin tests no es solo frágil — es una deuda cognitiva que eventualmente pagarás
Los Secretos que Este Libro No Pudo Enseñarte
Hay conocimiento que solo viene con la práctica:
- 🧠 La intuición para saber cuándo un problema requiere un diccionario vs un árbol vs una base de datos
- 🔍 El instinto de seguridad que te hace preguntar “¿qué pasa si alguien envía este input malicioso?”
- 🏗️ La arquitectura que divide el código en módulos que pueden evolucionar independientemente
- 🤝 La colaboración — código que otros pueden entender, modificar, y mejorar
Ese conocimiento no viene de libros. Viene de construir cosas reales, romperlas, y reconstruirlas mejor.
Tu Próximo Paso
Elige uno de los proyectos del Capítulo 7.4. Construye uno completo, con todos los criterios de la checklist. Ponlo en GitHub. Escribe el README.
No esperes a saber más. No esperes a que el proyecto sea perfecto. El código que funciona imperfectamente hoy vale infinitamente más que el código perfecto que no existe.
“La diferencia entre un programador y alguien que usa IA para programar no es la herramienta. Es la comprensión de por qué el código hace lo que hace, y la capacidad de dirigirlo hacia donde necesitas ir.”
Python te da el lenguaje. La IA te da el copiloto. La comprensión profunda es lo que hace que el viaje valga la pena.
Ahora ve a construir algo. 🐍🚀
📚 Recursos del Libro
Documentación Oficial
- 🌐 Python 3.14 Docs — La fuente primaria
- 📖 PEP Index — Todas las propuestas de mejora de Python
- 🎓 Real Python — Tutoriales de alta calidad
Práctica
- 🎮 Exercism.io — Python Track — Ejercicios con mentores reales
- 🎮 LeetCode — Para entrevistas técnicas
- 📊 Kaggle — Data Science con datasets reales
Comunidad
- 💬 r/Python — Novedades y discusión
- 💬 Python Discord — Ayuda en tiempo real
- 🎧 Talk Python to Me — El podcast de Python
Libros Complementarios
- 📚 Fluent Python (2nd Ed) — Luciano Ramalho — El libro de Python avanzado más importante
- 📚 Python Cookbook — David Beazley — Recetas para problemas comunes
- 📚 Architecture Patterns with Python — Gratis online, patrones de producción
Escrito por Jafet Brito · Security Researcher · Zero Trust Mindset Python 3.14 · Junio 2026 · Este libro es un documento vivo que se actualiza con el lenguaje.
🐍 Augmented Python: The Book the Other 9 Million Didn’t Write
By Jafet Brito · Security Researcher · Published June 11, 2026
“Python isn’t a programming language. It’s the language humanity uses to talk to machines in 2026.”
⚠️ Why This Book Is Different
There are 9 million Python tutorials on the internet. This is not one more.
What makes this book different:
- 🤖 AI as permanent copilot — every chapter includes how to use Claude, Gemini, or GitHub Copilot to accelerate that specific topic
- 🔐 Zero Trust mindset from day one — security is not a chapter at the end, it’s a philosophy running through all the code
- 🎯 Real projects, not toy exercises — each part ends with something you can put in your portfolio
- 📅 Python 3.14 + 2026 updates — t-strings, free-threading, JIT compiler, and what actually matters
- 🌐 The bridge to LLMs — Python as the gateway to the generative AI ecosystem
- ⚗️ The “secrets” basic tutorials never mention — production patterns, anti-patterns, and decisions that separate amateur from professional code
🗺️ Book Map
📖 AUGMENTED PYTHON — COMPLETE STRUCTURE
PART 0: Before the Code — The Mindset
└─ Ch 0.1: Why Python won
└─ Ch 0.2: Setting up your environment like a professional
└─ Ch 0.3: Your first AI copilot
PART I: The Fundamentals (No Shortcuts)
PART II: Data Structures — The Building Blocks of Reality
PART III: Intermediate Python — Where Everything Changes
PART IV: Python for the Real World
PART V: Python + AI — The 2026 Differentiator
PART VI: Python for Cybersecurity and OSINT
PART VII: Python in Production
Ch 0.1: Why Python Won
Python didn’t win because it’s the “best” language — it’s not the fastest, not the most statically typed, not the most memory efficient. Python won because of the philosophy of readability.
Python has a document called PEP 20 — The Zen of Python. Type import this in any Python interpreter to see it. The most important principles:
“Beautiful is better than ugly.” “Explicit is better than implicit.” “Readability counts.”
Python 3.14 — The Performance Leap (October 7, 2025):
- 🚀 Free-Threading (PEP 779) — GIL removal, true parallelism in threads
- ⚡ Experimental JIT Compiler — performance acceleration for CPU-intensive loops
- 🧵 T-strings (PEP 750) — f-string evolution with customizable processing
- 📝 Deferred annotations — improved startup time and runtime performance
- 🎨 Colored REPL with multi-line — the interactive experience Python always deserved
- 🆔 UUID v7 — native in the
uuidmodule, time-ordered UUIDs
Ch 0.2: Set Up Your Environment Like a Professional
# Install pyenv (manages multiple Python versions)
curl https://pyenv.run | bash
# Install Python 3.14
pyenv install 3.14.0
pyenv global 3.14.0
# Install uv — the modern package manager (100x faster than pip)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create your first professional project
uv init my-project
cd my-project
uv add requests anthropic pydantic
uv add --dev pytest ruff mypy
🔐 Zero Trust from day 1: The
.envfile contains your credentials and API keys. Never commit it to git. Always create a.gitignorethat excludes it before writing your first line of code.
Ch 0.3: Your First AI Copilot
Mode 1 — The Tutor (for understanding concepts):
"I'm a Python beginner. Explain what a function is
like a chef making reusable recipes. Then give me
an example of a function that calculates price with tax,
and explain each line. Finally, ask me 3 questions
to verify I understood."
Mode 2 — The Pair Programmer (for writing code):
"Write a Python function that:
- Receives a list of emails
- Validates each has correct format
- Returns a tuple: (valid_emails, invalid_emails)
- Includes modern Python 3.14 type hints
- Includes docstring with examples
- Is testable with pytest"
Mode 3 — The Debugger (for fixing errors):
"This Python code gives me the following error.
Instead of giving me the solution directly,
explain WHAT is causing the error and WHY,
then ask me questions to guide me to find the solution myself."
PART I: The Fundamentals
The First Truth: Everything is an Object
# Everything in Python is an object
x = 42
print(type(x)) # <class 'int'>
print(id(x)) # Memory address
print(dir(x)) # All methods an int has
# Python caches integers -5 to 256 as singletons
a = 256
b = 256
print(a is b) # True — same object in memory
a = 257
b = 257
print(a is b) # False — different objects
# Always use == for value comparison, is only for identity (like None)
# The float trap that destroys financial applications:
print(0.1 + 0.2) # 0.30000000000000004 ⚠️
from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2')) # 0.2 ✅
# Type hints — professional Python in 2026
def calculate_tax(price: float, rate: float = 0.16) -> float:
"""
Calculates price with tax included.
Args:
price: Base price without tax
rate: Tax rate (default 16%)
Returns:
Total price with tax
"""
return price * (1 + rate)
Functions — The Art of Not Repeating Yourself
# Professional function with all modern elements
def process_payment(
amount: float,
currency: str = "USD",
*, # Everything after this is keyword-only
description: str = "",
validate: bool = True
) -> dict:
if validate and amount <= 0:
raise ValueError(f"Amount must be positive, received: {amount}")
return {
"status": "success",
"amount": amount,
"currency": currency,
"description": description
}
# Decorators — the pattern nobody explains well
import functools
import time
def retry(max_attempts: int = 3, wait: float = 1.0):
"""Decorator that retries a function if it fails."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
last_error = e
if attempt < max_attempts - 1:
time.sleep(wait)
raise last_error
return wrapper
return decorator
@retry(max_attempts=3, wait=0.5)
def call_external_api(endpoint: str) -> dict:
import requests
return requests.get(endpoint, timeout=5).json()
Python 3.14 Key Features
# 1. T-STRINGS — PEP 750
# Problem: f-strings evaluate immediately, no control
name = "<script>alert('XSS')</script>"
# ❌ Dangerous in HTML:
html = f"<div>{name}</div>" # XSS injection
# ✅ T-strings allow custom processing:
from html import escape
def html_safe(template):
parts = []
for part in template:
if isinstance(part, str):
parts.append(part)
else:
parts.append(escape(str(part.value)))
return "".join(parts)
safe_html = html_safe(t"<div>{name}</div>")
# "<div><script>alert('XSS')</script></div>"
# 2. UUID7 — native time-ordered UUIDs
from uuid import uuid7
new_id = uuid7() # Ordered by time + random — better for DB indexing
# 3. Pattern matching — even more powerful
def process_api_response(response: dict) -> str:
match response:
case {"status": "success", "data": {"user": {"name": name}}}:
return f"Welcome, {name}"
case {"status": "error", "code": 401}:
return "Unauthorized"
case {"status": "error", "code": code, "message": msg}:
return f"Error {code}: {msg}"
case _:
return "Unknown response"
PART V: Python + AI
Claude API from Python
import os
import anthropic
from anthropic import Anthropic
import instructor
from pydantic import BaseModel, Field
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
# Basic call
def ask_claude(question: str, system: str = None) -> str:
kwargs = {
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [{"role": "user", "content": question}]
}
if system:
kwargs["system"] = system
return client.messages.create(**kwargs).content[0].text
# Streaming
def claude_stream(prompt: str):
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=2048,
messages=[{"role": "user", "content": prompt}]
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
# Prompt caching — 90% discount on cached tokens
def analyze_with_large_context(document: str, question: str) -> str:
return client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[{
"type": "text",
"text": f"You are a document analyzer. Document:\n\n{document}",
"cache_control": {"type": "ephemeral"}
}],
messages=[{"role": "user", "content": question}]
).content[0].text
# Structured outputs with instructor
claude_instructor = instructor.from_anthropic(
Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
)
class SentimentAnalysis(BaseModel):
sentiment: str = Field(description="positive, negative, or neutral")
confidence: float = Field(ge=0, le=1)
keywords: list[str]
summary: str
def analyze_sentiment(text: str) -> SentimentAnalysis:
return claude_instructor.messages.create(
model="claude-sonnet-4-6",
max_tokens=500,
response_model=SentimentAnalysis,
messages=[{"role": "user", "content": f"Analyze sentiment of: '{text}'"}]
)
PART VI: Python for Cybersecurity
Zero Trust in Code
# Secrets management — NEVER hardcode
import os
from dotenv import load_dotenv
load_dotenv() # Loads .env automatically
api_key = os.environ["API_KEY"] # KeyError if missing = intentional fail-fast
# Secure password hashing
import bcrypt
import secrets
def hash_password(password: str) -> str:
salt = bcrypt.gensalt(rounds=12)
return bcrypt.hashpw(password.encode(), salt).decode()
def verify_password(password: str, stored_hash: str) -> bool:
return bcrypt.checkpw(password.encode(), stored_hash.encode())
# Secure token generation
session_token = secrets.token_hex(32)
# Log analysis for threat detection
import re
from collections import Counter
class LogAnalyzer:
PATTERNS = {
"brute_force": re.compile(r"Failed password for .+ from (\d+\.\d+\.\d+\.\d+)"),
"sql_injection": re.compile(r"(UNION|SELECT|DROP).*(\bOR\b|\bAND\b)", re.IGNORECASE),
"xss": re.compile(r"<script|javascript:|onerror=", re.IGNORECASE),
"path_traversal": re.compile(r"\.\./|%2e%2e", re.IGNORECASE)
}
def analyze_line(self, line: str, line_num: int = 0) -> list:
alerts = []
for threat_type, pattern in self.PATTERNS.items():
if match := pattern.search(line):
alerts.append({
"type": threat_type,
"line": line_num,
"match": match.group(0),
"severity": "HIGH" if threat_type in ["sql_injection", "xss"] else "MEDIUM"
})
return alerts
PART VII: Python in Production
FastAPI — Modern APIs
from fastapi import FastAPI, HTTPException, Depends, status
from pydantic import BaseModel, EmailStr, Field
from typing import Annotated
app = FastAPI(title="My API", version="1.0.0")
class CreateUser(BaseModel):
name: str = Field(min_length=1, max_length=100)
email: EmailStr
password: str = Field(min_length=8)
class UserResponse(BaseModel):
id: int
name: str
email: str
active: bool
model_config = {"from_attributes": True}
@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(
data: CreateUser,
db: Annotated[Session, Depends(get_db)]
):
if db.query(User).filter(User.email == data.email).first():
raise HTTPException(status_code=409, detail="Email already registered")
user = User(
name=data.name,
email=data.email,
password_hash=hash_password(data.password)
)
db.add(user)
db.commit()
db.refresh(user)
return user
@app.get("/health")
async def health_check():
return {"status": "healthy", "version": "1.0.0"}
🏁 Epilogue: The Augmented Programmer
Python is not just a language. It’s the common language between humans and the new intelligence layer we’re building over the world.
The knowledge this book could not teach you — the intuition for knowing when to use a dictionary vs a tree vs a database, the security instinct that makes you ask “what if someone sends this malicious input?”, the architecture that divides code into modules that can evolve independently — comes only from practice.
Choose one of the Chapter 7.4 projects. Build one completely. Put it on GitHub.
“The difference between a programmer and someone who uses AI to program isn’t the tool. It’s the understanding of why the code does what it does, and the ability to direct it toward where you need to go.”
Python gives you the language. AI gives you the copilot. Deep understanding is what makes the journey worth it.
Now go build something. 🐍🚀
📚 Book Resources
- 🌐 Python 3.14 Official Docs
- 🎓 Real Python
- 🎮 Exercism.io — Python Track
- 📊 Kaggle
- 📚 Fluent Python (2nd Ed)
- 📚 Architecture Patterns with Python (free)
Written by Jafet Brito · Security Researcher · Zero Trust Mindset Python 3.14 · June 2026 · This book is a living document that updates with the language.