Capítulo 7 de 14 8 secciones 9 min

Módulos, paquetes y entornos virtuales

Salir del cuaderno, importar sin romper nada, y la respuesta a "en mi máquina funcionaba".

Un módulo es un archivo .py con código que puedes reutilizar, y se trae con import. Python ya viene con una librería estándar enorme que no hace falta instalar. Un entorno virtual es una carpeta con su propio Python y sus propias librerías, y sirve para que dos proyectos con versiones distintas no se peleen entre sí.

Hola! Salgamos del cuaderno

Todo lo de los capítulos anteriores vivía en una celda. Perfecto para aprender, y en algún momento se te queda chico 🌱

Cuando una función te sirve en tres cuadernos distintos, copiarla tres veces es pedir problemas: arreglas una y las otras dos siguen mal. Para eso están los módulos.

Un módulo es un archivo

Nada más. Si guardas esto como utiles.py:

# utiles.py
IGV = 0.18

def con_igv(monto):
    return round(monto * (1 + IGV), 2)

def limpia_ciudad(texto):
    return texto.strip().title()

Desde otro archivo o cuaderno en la misma carpeta lo traes así:

import utiles

print(utiles.con_igv(100))
print(utiles.IGV)

Ese punto se lee como "de dentro de". utiles.con_igv es "la función con_igv de dentro de utiles".

Las cuatro formas de importar

import math
from math import sqrt
from math import sqrt as raiz
import statistics as stats

print(math.sqrt(16))
print(sqrt(16))
print(raiz(16))
print(stats.mean([10, 20, 30]))
4.0
4.0
4.0
20

Cuál usar:

  • 📦 import math cuando vas a usar varias cosas del módulo. Se lee de dónde viene cada una.
  • 🎯 from math import sqrt cuando usas una sola y mucho.
  • 🏷️ as para acortar. Aquí hay convenciones que todo el mundo respeta: import pandas as pd, import numpy as np. Úsalas, que así tu código se lee igual que el de cualquiera.

Y una que no debes usar aunque la veas por ahí:

from math import *      # NO

Eso trae todo y llena tu espacio de nombres de cosas que no sabes que están. Si dos módulos tienen una función con el mismo nombre, el segundo pisa al primero sin avisarte 🙃

La librería estándar, que ya tienes

Python viene con muchísimo incluido. Estos son los que de verdad vas a usar en datos:

from datetime import date, datetime, timedelta

hoy = date(2026, 8, 15)
compra = date(2026, 5, 30)

print((hoy - compra).days)
print(hoy + timedelta(days=30))
print(hoy.strftime('%d/%m/%Y'))
print(datetime.strptime('29/06/2025', '%d/%m/%Y').date())
77
2026-09-14
15/08/2026
2025-06-29

Esas cuatro líneas son el 80% de lo que se hace con fechas: cuántos días pasaron, sumar días, mostrarla bonita y leerla desde un texto.

strftime convierte fecha a texto y strptime convierte texto a fecha. La p es de "parse". Yo las confundí durante años, así que no te sientas mal 😄

from collections import Counter

canales = ['Web', 'Tienda', 'Web', 'WhatsApp', 'Web', 'Tienda']

conteo = Counter(canales)
print(conteo)
print(conteo.most_common(2))
Counter({'Web': 3, 'Tienda': 2, 'WhatsApp': 1})
[('Web', 3), ('Tienda', 2)]

¿Te acuerdas del bucle con .get(c, 0) + 1 del capítulo 3? Esto hace lo mismo en una línea. Yo te hice escribir el bucle a propósito, para que entiendas qué hay debajo 💜

import json

texto = '{"ciudad": "Lima", "monto": 480.37, "pagado": true}'
venta = json.loads(texto)

print(venta['ciudad'], venta['monto'])
print(type(venta['pagado']), venta['pagado'])
Lima 480.37
<class 'bool'> True

Fíjate en que el true en minúscula del JSON se convirtió en el True de Python. Esa traducción la hace json sola, y es la razón por la que nunca hay que parsear JSON a mano.

MóduloPara qué
datetimeFechas y diferencias entre fechas
collectionsCounter, defaultdict
jsonLeer y escribir JSON
csvCSV sin pandas, cuando el archivo es enorme
pathlibRutas de archivos que funcionan en Windows y en Mac
statisticsMedia, mediana, desviación, sin instalar nada
reExpresiones regulares, para buscar patrones en texto

Antes de instalar una librería para algo, pregúntate si la estándar ya lo hace. Muchas veces sí 🌸

pathlib: rutas que no se rompen

Las rutas escritas a mano son una fuente clásica de dolor, porque Windows usa barras al revés que Mac y Linux.

from pathlib import Path

carpeta = Path('datos')
archivo = carpeta / 'ventas' / 'enero.csv'

print(archivo)
print(archivo.name)
print(archivo.stem)
print(archivo.suffix)
print(archivo.exists())
datos/ventas/enero.csv
enero.csv
enero
.csv
False

Ese / entre carpetas es magia de pathlib: arma la ruta con el separador correcto de cada sistema.

Y ese .exists() en False es tu mejor amigo. Porque si no preguntas, pasa esto:

open('datos/ventas/enero.csv')
FileNotFoundError: [Errno 2] No such file or directory: 'datos/ventas/enero.csv'

Y ojo con cómo se lee ese mensaje: dice la ruta relativa, así que no te dice desde dónde estaba buscando. Cuando te pase, esta línea te lo aclara en un segundo:

print(Path.cwd())
/home/user/MissYeraWeb

Ahí sale la carpeta desde la que Python está mirando. El 90% de los FileNotFoundError son eso: el archivo existe, pero está en otro sitio del que tú creías 📁

Así que la costumbre buena es preguntar antes:

ruta = Path('datos/ventas/enero.csv')

if ruta.exists():
    print('leyendo', ruta)
else:
    print(f'no encuentro {ruta} desde {Path.cwd().name}')
no encuentro datos/ventas/enero.csv desde MissYeraWeb

Entornos virtuales, o "en mi máquina funcionaba"

Esta parte te la explico porque tarde o temprano te va a pasar y quiero que sepas qué es.

Imagina que tienes dos proyectos. Uno viejo que necesita pandas 1.5 y uno nuevo que necesita pandas 2.2. Si instalas todo en el mismo Python, la segunda instalación pisa a la primera y el proyecto viejo deja de funcionar 😖

Un entorno virtual es una carpeta con su propio Python y sus propias librerías. Cada proyecto tiene el suyo y no se pisan.

# Crear el entorno, una sola vez por proyecto
python3 -m venv .venv

# Activarlo (Mac y Linux)
source .venv/bin/activate

# Activarlo (Windows)
.venv\Scripts\activate

# Ya dentro: instalar lo del proyecto
pip install pandas matplotlib

# Guardar la lista exacta
pip freeze > requirements.txt

# Salir
deactivate

Cuando está activado, tu terminal muestra (.venv) al principio de la línea. Esa es la señal de que estás dentro.

Ese requirements.txt es el archivo que hace que otra persona pueda reproducir tu entorno exacto:

pip install -r requirements.txt

Y dos cosas que se olvidan siempre:

  • 🚫 La carpeta .venv no se sube a git. Se pone en el .gitignore. Lo que se sube es el requirements.txt.
  • 💻 Si usas un editor como VS Code, hay que decirle que use ese Python. Si no, te va a decir que pandas no está instalado aunque lo esté.

En Colab nada de esto aplica: cada cuaderno ya es su propio entorno aislado, y por eso es tan cómodo para aprender.

Organizar tu propio código

Cuando un proyecto crece, esta estructura funciona y es la que yo uso:

mi_proyecto/
    .venv/                  no se sube a git
    datos/
        ventas.csv
    src/
        __init__.py
        limpieza.py
        metricas.py
    cuadernos/
        exploracion.ipynb
    requirements.txt
    README.md

El __init__.py vacío es lo que convierte una carpeta en paquete, y permite importar así:

from src.limpieza import limpia_ciudad
from src.metricas import ticket_promedio

La regla que te va a servir: el cuaderno explora, el módulo guarda. Cuando una función del cuaderno ya funciona bien y la vas a volver a usar, se muda a un .py. Así el cuaderno queda para probar cosas y el código bueno vive en un solo sitio 🌟

El bloque que aparece al final de los scripts

def principal():
    print('esto es el trabajo de verdad')

if __name__ == '__main__':
    principal()
esto es el trabajo de verdad

Ese if significa "corre esto solo si me estás ejecutando a mí directamente, no si me estás importando".

Sin él, importar tu módulo desde otro sitio ejecutaría todo el script entero. Y sí, se ve raro la primera vez. Con verlo tres veces se te queda 😄

Ejercicios

1. Días entre fechas

Un cliente compró el 15 de enero y volvió el 30 de junio de 2026. Calcula cuántos días estuvo sin comprar.

from datetime import date

compra_anterior = date(2026, 1, 15)
compra_nueva = date(2026, 6, 30)

print((compra_nueva - compra_anterior).days, 'días sin comprar')
166 días sin comprar
2. La fecha que vino como texto

Del archivo te llega la fecha '29/06/2025' en formato peruano. Conviértela y muéstrala en formato internacional, que es el que no se presta a confusión.

from datetime import datetime

f = datetime.strptime('29/06/2025', '%d/%m/%Y').date()
print(f, f.strftime('%Y-%m-%d'))
2025-06-29 2025-06-29
3. El canal más usado

Con Counter, encuentra el canal más frecuente y cuántas veces aparece.

from collections import Counter

canales = ['Web', 'Tienda', 'Web', 'WhatsApp', 'Web', 'Marketplace', 'Tienda']
top = Counter(canales).most_common(1)[0]

print(f'{top[0]} con {top[1]} ventas')
Web con 3 ventas
4. Fecha de vencimiento

Una factura del 15 de agosto de 2026 vence a los 45 días. Calcula la fecha y en qué día de la semana cae.

from datetime import date, timedelta

emision = date(2026, 8, 15)
vence = emision + timedelta(days=45)

dias = ['lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo']
print(vence, dias[vence.weekday()])
2026-09-29 martes

weekday() devuelve 0 para lunes. Un clásico error por uno es asumir que empieza en domingo, como en Excel.

5. Un JSON de una API

Parsea una respuesta con varias ventas y saca el total.

import json

respuesta = ('{"ventas": ['
             '{"ciudad": "Lima", "monto": 480.37},'
             '{"ciudad": "Cusco", "monto": 154.83}]}')

datos = json.loads(respuesta)
print(round(sum(v['monto'] for v in datos['ventas']), 2))
635.2
6. Rutas sin romperse

Arma la ruta al archivo de ventas de agosto y muestra su nombre sin extensión.

from pathlib import Path

ruta = Path('ventas') / '2026' / 'agosto.csv'
print(ruta, '|', ruta.stem, '|', ruta.suffix)
ventas/2026/agosto.csv | agosto | .csv
7. Estadística sin instalar nada

Con el módulo statistics, saca media y mediana de unos montos y explica por qué difieren.

import statistics as stats

montos = [120, 150, 180, 200, 5000]

print(stats.mean(montos))
print(stats.median(montos))
1130
180

La media se fue a 1.130 por culpa de un solo valor de 5.000. La mediana ni se inmutó. Por eso con dinero casi siempre se reporta la mediana, y eso lo desarrollo en la guía de estadística.

8. Buscar un patrón en texto

Con el módulo re, saca todos los códigos de cliente de un texto libre. Los códigos son una C y cuatro dígitos.

import re

nota = 'Revisar C0045 y C0325, tambien el pedido de C0260 de ayer'
print(re.findall(r'C\d{4}', nota))
['C0045', 'C0325', 'C0260']

Ese \d{4} significa "cuatro dígitos". Las expresiones regulares dan para un libro entero, pero con findall y dos patrones simples ya resuelves muchísimo.

Lo que te llevas

  • 📄 Un módulo es un archivo .py, y el punto se lee como "de dentro de".
  • 🏷️ import pandas as pd y import numpy as np son convenciones: úsalas.
  • 🧰 Antes de instalar algo, mira si la librería estándar ya lo hace.
  • 📁 pathlib para rutas, siempre, y .exists() antes de leer.
  • 🧪 Un entorno virtual por proyecto, requirements.txt al repo y .venv al .gitignore.
  • ✍️ El cuaderno explora, el módulo guarda.

En el capítulo 8 vemos objetos, y te adelanto que va a ser más corto de lo que temes: para datos hace falta entenderlos, no diseñarlos.

Que tengas un hermoso día! 🌟

¿Tienes alguna duda o consulta?