trubidata
Lección 9 de 12·24 min

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:

python
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:

python
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 modulo es la forma más clara: al leer math.sqrt sabés de dónde viene.
  • from modulo import nombre es 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:

python
# 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
python
# archivo: main.py
import conversiones

print(conversiones.millas_a_km(10))
print(conversiones.celsius_a_fahrenheit(25))

Ejecutá python3 main.py:

text
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:

python
# 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:

python
# 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.py muestra Probando: 1.60934.
  • python3 main.py importa conversiones sin ejecutar la prueba.

Un patrón muy usado es poner el programa en una función main() y llamarla desde ahí:

python
def main() -> None:
    print("Arranca el programa")


if __name__ == "__main__":
    main()
text
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:

text
mi_proyecto/
├── main.py
└── utilidades/
    ├── __init__.py
    ├── textos.py
    └── numeros.py
python
# archivo: utilidades/textos.py
def normalizar(texto: str) -> str:
    return " ".join(texto.split()).title()
python
# archivo: utilidades/numeros.py
def porcentaje(parte: float, total: float) -> float:
    if total == 0:
        return 0.0
    return parte / total * 100
python
# 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:

text
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

python
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

python
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
  • date es una fecha; datetime, fecha y hora; timedelta, una duración.
  • Restar dos fechas da un timedelta; .days te da los días.
  • strftime convierte a texto con el formato que quieras (%d día, %m mes, %Y año, %H hora, %M minutos).
  • date.fromisoformat("AAAA-MM-DD") convierte un texto a fecha, y lanza ValueError si 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:

python
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

python
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.
  • json y csv: 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)

bash
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)

bash
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:

python
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.py o json.py propio 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 pip fuera del entorno. Queda instalado en el Python del sistema y el proyecto no lo ve. Activá el entorno antes o usá uv add.
  • Subir .venv al repositorio. Pesa mucho y no sirve en otra máquina. Compartí pyproject.toml (o requirements.txt).

Practicá

  1. Creá un módulo geometria.py con area_circulo(radio) y area_rectangulo(base, altura), y con pruebas bajo if __name__ == "__main__":. Desde main.py, importalo y calculá el área de un círculo de radio 3 con dos decimales. Verificá que al correr main.py no se ejecuten las pruebas.
  2. Simulá 1000 tiradas de un dado con random.randint (fijá random.seed(1)) y contá con Counter cuántas veces salió cada cara. Mostralas de la 1 a la 6.
  3. Escribí dias_hasta(fecha_texto: str) -> int que 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.
  4. Dado un texto, usá Counter para mostrar las 3 palabras más frecuentes de más de 3 letras, ignorando mayúsculas.

Soluciones

Ejercicio 1:

python
# 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))
python
# archivo: main.py
import geometria

print(f"Área: {geometria.area_circulo(3):.2f}")
text
Área: 28.27

Ejercicio 2:

python
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:

python
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:

python
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}")
text
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 con import modulo o from 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) o python3 -m venv + pip install.

← Volver al temario