Módulos, paquetes y biblioteca estándar
En esta guía (17)
Cuando un programa crece, tenerlo todo en un archivo se vuelve inmanejable. En esta lección vas a aprender a repartir el código en módulos (archivos .py) y paquetes (carpetas de módulos), a usar import, a entender para qué sirve el famoso if __name__ == "__main__":, a aprovechar la biblioteca estándar que viene con Python y a instalar librerías de terceros dentro de un entorno virtual.
import: usar código de otro módulo
Un módulo es simplemente un archivo .py. Python trae muchos ya hechos. Para usar uno, lo importás:
import math
print(math.sqrt(81)) # 9.0
print(math.pi) # 3.141592653589793
print(math.ceil(4.1)) # 5 (redondea hacia arriba)
print(math.floor(4.9)) # 4 (redondea hacia abajo)
Con import math tenés acceso a todo lo del módulo a través del prefijo math.. Hay otras formas:
from math import sqrt, pi # importa solo esos nombres, sin prefijo
print(sqrt(16) * pi) # 12.566370614359172
import math as m # importa con un alias
print(m.floor(2.7)) # 2
import moduloes la forma más clara: al leermath.sqrtsabés de dónde viene.from modulo import nombrees cómoda cuando usás mucho una o dos cosas.- Evitá
from modulo import *(importa todo sin prefijo): no se sabe de dónde viene cada nombre y puede pisar los tuyos.
Los import van al principio del archivo, por convención.
Tus propios módulos
Cualquier archivo .py es un módulo que podés importar desde otro archivo de la misma carpeta. Creá dos archivos:
# archivo: conversiones.py
"""Funciones para convertir unidades."""
KM_POR_MILLA = 1.60934
def millas_a_km(millas: float) -> float:
return millas * KM_POR_MILLA
def celsius_a_fahrenheit(c: float) -> float:
return c * 9 / 5 + 32
# archivo: main.py
import conversiones
print(conversiones.millas_a_km(10))
print(conversiones.celsius_a_fahrenheit(25))
Ejecutá python3 main.py:
16.0934
77.0
El nombre del módulo es el nombre del archivo sin .py. Por eso no nombres tus archivos igual que módulos de Python: si creás un math.py o un random.py en tu carpeta, import random va a importar el tuyo en vez del de Python, y nada va a funcionar como esperás.
if __name__ == "__main__":
Supongamos que en conversiones.py agregás unas pruebas al final:
# al final de conversiones.py
print("Probando:", millas_a_km(1))
Ahora, cuando main.py hace import conversiones, esa línea también se ejecuta: importar un módulo ejecuta todo su código. Para que algo corra solo cuando ejecutás el archivo directamente, y no cuando se importa, se usa:
# archivo: conversiones.py
KM_POR_MILLA = 1.60934
def millas_a_km(millas: float) -> float:
return millas * KM_POR_MILLA
if __name__ == "__main__":
print("Probando:", millas_a_km(1))
Cómo funciona: Python le pone a cada módulo una variable __name__. Si el archivo se ejecuta directamente (python3 conversiones.py), __name__ vale "__main__". Si se importa, vale "conversiones". Entonces:
python3 conversiones.pymuestraProbando: 1.60934.python3 main.pyimportaconversionessin ejecutar la prueba.
Un patrón muy usado es poner el programa en una función main() y llamarla desde ahí:
def main() -> None:
print("Arranca el programa")
if __name__ == "__main__":
main()
Arranca el programa
Paquetes: carpetas de módulos
Un paquete es una carpeta que agrupa módulos relacionados. Lleva un archivo __init__.py (puede estar vacío) que le indica a Python que esa carpeta es un paquete:
mi_proyecto/
├── main.py
└── utilidades/
├── __init__.py
├── textos.py
└── numeros.py
# archivo: utilidades/textos.py
def normalizar(texto: str) -> str:
return " ".join(texto.split()).title()
# archivo: utilidades/numeros.py
def porcentaje(parte: float, total: float) -> float:
if total == 0:
return 0.0
return parte / total * 100
# archivo: main.py
from utilidades.textos import normalizar
from utilidades import numeros
print(normalizar(" ana pérez "))
print(f"{numeros.porcentaje(30, 120):.1f}%")
Desde la carpeta mi_proyecto, python3 main.py muestra:
Ana Pérez
25.0%
El punto separa paquete y módulo: utilidades.textos es "el módulo textos dentro del paquete utilidades". Siempre ejecutá main.py desde la carpeta raíz del proyecto, que es donde Python busca los imports.
La biblioteca estándar
Python viene "con las pilas puestas": trae cientos de módulos listos para usar sin instalar nada. Estos son algunos que vas a usar seguido.
random: números aleatorios
import random
random.seed(7) # fija la secuencia, para que sea repetible
print(random.randint(1, 6)) # entero entre 1 y 6, ambos incluidos
print(random.choice(["rojo", "verde", "azul"]))
print(random.random()) # float entre 0 y 1
mazo = [1, 2, 3, 4, 5]
random.shuffle(mazo) # mezcla la lista (la modifica)
print(mazo)
print(random.sample(range(1, 50), 3)) # 3 valores distintos
random.seed() hace que los "aleatorios" salgan siempre iguales en cada ejecución. Es útil para probar un programa; en uso real no se fija.
datetime: fechas y horas
from datetime import date, datetime, timedelta
hoy = date(2026, 10, 13) # en un programa real: date.today()
print(hoy) # 2026-10-13
print(hoy.year, hoy.month, hoy.day) # 2026 10 13
en_30_dias = hoy + timedelta(days=30)
print(en_30_dias) # 2026-11-12
fin_de_anio = date(2026, 12, 31)
print((fin_de_anio - hoy).days) # 79
print(hoy.strftime("%d/%m/%Y")) # 13/10/2026
print(date.fromisoformat("2026-03-01")) # 2026-03-01
momento = datetime(2026, 10, 13, 18, 30)
print(momento.strftime("%d/%m %H:%M")) # 13/10 18:30
datees una fecha;datetime, fecha y hora;timedelta, una duración.- Restar dos fechas da un
timedelta;.dayste da los días. strftimeconvierte a texto con el formato que quieras (%ddía,%mmes,%Yaño,%Hhora,%Mminutos).date.fromisoformat("AAAA-MM-DD")convierte un texto a fecha, y lanzaValueErrorsi el texto no es una fecha válida.
collections.Counter: contar en una línea
En la lección 5 contaste con un diccionario y get. Counter hace lo mismo directamente:
from collections import Counter
votos = ["rojo", "azul", "rojo", "verde", "rojo", "azul"]
conteo = Counter(votos)
print(conteo) # Counter({'rojo': 3, 'azul': 2, 'verde': 1})
print(conteo["rojo"]) # 3
print(conteo["negro"]) # 0 (no da KeyError)
print(conteo.most_common(2)) # [('rojo', 3), ('azul', 2)]
Conocer lo que ya trae el lenguaje te ahorra escribir (y depurar) código que otros ya resolvieron.
pathlib y os: archivos, carpetas y entorno
from pathlib import Path
import os
carpeta = Path("datos")
archivo = carpeta / "ventas.csv" # une rutas con / en cualquier sistema operativo
print(archivo) # datos/ventas.csv (en Windows: datos\ventas.csv)
print(archivo.name) # ventas.csv
print(archivo.stem) # ventas
print(archivo.suffix) # .csv
print(archivo.exists()) # False (si no existe)
print(Path.cwd()) # la carpeta desde donde corrés el programa
print(os.environ.get("HOME", "no definida")) # una variable de entorno
pathlib es la forma moderna de trabajar con rutas; la vas a usar a fondo en la lección 10. Del módulo os lo más útil al empezar es os.environ.get(...), que lee variables de entorno: valores de configuración que vienen del sistema y no del código.
Otros que conviene conocer
statistics:mean,median,stdev.jsonycsv: leer y escribir esos formatos (lección 10).itertools: combinaciones, permutaciones y recorridos avanzados.time: pausas (time.sleep(1)) y medir cuánto tarda algo.
La documentación oficial tiene la lista completa, en español.
Librerías de terceros y entornos virtuales
Además de la biblioteca estándar, hay cientos de miles de librerías publicadas en PyPI (el repositorio oficial de paquetes de Python) que se instalan aparte, por ejemplo requests para hacer pedidos HTTP o rich para mostrar tablas con colores en la terminal.
El problema: si instalás todo en el Python de tu sistema, los proyectos se mezclan. Un proyecto necesita la versión 1 de una librería y otro la versión 2, y no pueden convivir. La solución es un entorno virtual: una carpeta (por convención, .venv) con un Python y sus librerías solo para ese proyecto.
Con uv (recomendado)
uv init gastos # crea la carpeta del proyecto con un pyproject.toml
cd gastos
uv add rich # instala rich en el entorno del proyecto y lo anota
uv run main.py # ejecuta usando ese entorno
uv crea el .venv solo, la primera vez que hace falta. En pyproject.toml queda la lista de dependencias del proyecto, y en uv.lock las versiones exactas, así cualquier persona que clone el proyecto lo reproduce con uv sync. Más detalle en la guía uv y entornos virtuales.
Con venv y pip (la forma clásica)
python3 -m venv .venv # crea el entorno virtual
source .venv/bin/activate # lo activa (macOS/Linux)
.venv\Scripts\activate # lo activa (Windows)
pip install rich # instala dentro del entorno
pip freeze > requirements.txt # guarda la lista de dependencias
deactivate # sale del entorno
Con el entorno activado, python y pip usan el del proyecto. Es fácil olvidarse de activarlo y terminar instalando en el lugar equivocado: uno de los motivos por los que uv simplifica las cosas.
En los dos casos, la carpeta .venv no se comparte (no se sube a Git): se comparte la lista de dependencias y cada uno recrea su entorno.
Un ejemplo con rich, después de instalarla:
from rich.console import Console
from rich.table import Table
tabla = Table(title="Gastos")
tabla.add_column("Categoría")
tabla.add_column("Total", justify="right")
tabla.add_row("comida", "$ 7,600.00")
tabla.add_row("transporte", "$ 2,000.00")
Console().print(tabla)
El curso no depende de ninguna librería externa: todo, incluido el proyecto final, se hace con la biblioteca estándar.
Errores comunes
ModuleNotFoundError: No module named 'x': la librería no está instalada en el entorno que estás usando, o tu módulo no está en la carpeta desde donde ejecutás. Revisá que el entorno virtual esté activo (o usáuv run) y que corras el programa desde la raíz del proyecto.- Nombrar un archivo como un módulo estándar. Un
random.pyojson.pypropio tapa al de Python. Renombralo (y borrá la carpeta__pycache__si quedó). - Código suelto en un módulo que se importa. Todo lo que no esté dentro de funciones se ejecuta al importar. Poné las pruebas bajo
if __name__ == "__main__":. ImportError: cannot import name 'x': el nombre no existe en ese módulo (revisá mayúsculas y que esté definido) o hay un import circular (A importa B y B importa A).- Instalar con
pipfuera del entorno. Queda instalado en el Python del sistema y el proyecto no lo ve. Activá el entorno antes o usáuv add. - Subir
.venval repositorio. Pesa mucho y no sirve en otra máquina. Compartípyproject.toml(orequirements.txt).
Practicá
- Creá un módulo
geometria.pyconarea_circulo(radio)yarea_rectangulo(base, altura), y con pruebas bajoif __name__ == "__main__":. Desdemain.py, importalo y calculá el área de un círculo de radio 3 con dos decimales. Verificá que al corrermain.pyno se ejecuten las pruebas. - Simulá 1000 tiradas de un dado con
random.randint(fijárandom.seed(1)) y contá conCountercuántas veces salió cada cara. Mostralas de la 1 a la 6. - Escribí
dias_hasta(fecha_texto: str) -> intque reciba una fecha en formato"AAAA-MM-DD"y devuelva cuántos días faltan desde hoy (date.today()). Si la fecha ya pasó, el resultado es negativo. - Dado un texto, usá
Counterpara mostrar las 3 palabras más frecuentes de más de 3 letras, ignorando mayúsculas.
Soluciones
Ejercicio 1:
# archivo: geometria.py
import math
def area_circulo(radio: float) -> float:
return math.pi * radio ** 2
def area_rectangulo(base: float, altura: float) -> float:
return base * altura
if __name__ == "__main__":
print("Prueba círculo r=1:", area_circulo(1))
print("Prueba rectángulo 2x3:", area_rectangulo(2, 3))
# archivo: main.py
import geometria
print(f"Área: {geometria.area_circulo(3):.2f}")
Área: 28.27
Ejercicio 2:
import random
from collections import Counter
random.seed(1)
tiradas = [random.randint(1, 6) for _ in range(1000)]
conteo = Counter(tiradas)
for cara in range(1, 7):
print(f"{cara}: {conteo[cara]}")
Cada cara debería aparecer alrededor de 167 veces (1000 / 6). Con la semilla fija, cada vez que lo corras vas a ver los mismos números.
Ejercicio 3:
from datetime import date
def dias_hasta(fecha_texto: str) -> int:
"""Días desde hoy hasta la fecha dada (negativo si ya pasó)."""
objetivo = date.fromisoformat(fecha_texto)
return (objetivo - date.today()).days
print(dias_hasta("2030-01-01"))
El resultado depende del día en que lo ejecutes. Si el texto no es una fecha válida, fromisoformat lanza ValueError, que podés atrapar como en la lección 8.
Ejercicio 4:
from collections import Counter
texto = """El sol sale por el este. El sol se pone por el oeste.
Mirar el cielo al atardecer es mirar cómo el sol se despide."""
palabras = []
for palabra in texto.lower().split():
palabra = palabra.strip(".,;:")
if len(palabra) > 3:
palabras.append(palabra)
for palabra, veces in Counter(palabras).most_common(3):
print(f"{palabra}: {veces}")
mirar: 2
sale: 1
este: 1
strip(".,;:") quita la puntuación pegada a las palabras (como en este.). Cuando hay empates, most_common respeta el orden en que aparecieron por primera vez.
Resumen
- Un módulo es un archivo
.py; se usa conimport moduloofrom modulo import nombre. - Importar un módulo ejecuta su código: poné las pruebas y el programa principal bajo
if __name__ == "__main__":. - Un paquete es una carpeta con
__init__.py; se importa con puntos:from paquete.modulo import nombre. - No nombres tus archivos como módulos de la biblioteca estándar.
- La biblioteca estándar resuelve mucho sin instalar nada:
math,random,datetime,collections,pathlib,os,statistics,json,csv. - Las librerías de terceros se instalan en un entorno virtual por proyecto:
uv add(recomendado) opython3 -m venv+pip install.