El código que solo funciona en tu máquina no está terminado. Este módulo cubre las prácticas que hacen que los proyectos de Python sean reproducibles y desplegables: entornos aislados, gestores de paquetes, archivos de bloqueo de versiones, contenedores y los editores en los que escriben código los profesionales.
Aprenderás
- Por qué cada proyecto necesita su propio entorno y cómo funciona
venv(demostrado en vivo) - Gestores de paquetes comparados:
pip,uv,pipx,conda/mambay los del sistema (apt,snap,brew,winget) - Fijar dependencias: archivos de requisitos, archivos de bloqueo y
pyproject.toml - Docker: empaquetar una aplicación con todo su entorno y desplegarla
- Editores e IDE: VS Code, PyCharm, Eclipse con PyDev, Vim/Neovim, JupyterLab, Spyder
- Una estructura de proyecto y hábitos que conviene adoptar desde el primer día
1. ¿Qué Python estoy ejecutando?
Rara vez hay un solo Python en una máquina: el del sistema operativo, uno de un gestor de paquetes, uno por proyecto. Sabe siempre cuál ejecuta tu código.
import sys, platform, site
print("intérprete :", sys.executable)
print("version :", platform.python_version())
print("prefijo :", sys.prefix) # el entorno al que pertenece este intérprete
print("prefijo base:", sys.base_prefix) # la instalación a partir de la que se creó
print("en un venv :", sys.prefix != sys.base_prefix)
2. Entornos aislados con venv
El proyecto A necesita pandas 1.5 y el proyecto B pandas 2.2: instalados globalmente, uno de los dos se
rompe. Un entorno virtual es una carpeta con su propio site-packages, así cada proyecto tiene
exactamente las versiones que necesita y nada se mezcla entre ellos. Creado en vivo a continuación:
import subprocess, tempfile, os, venv
env_dir = os.path.join(tempfile.mkdtemp(), "demo-env")
venv.create(env_dir, with_pip=True) # equivale a: python -m venv demo-env
py = os.path.join(env_dir, "bin", "python") if os.name != "nt" else os.path.join(env_dir, "Scripts", "python.exe")
def in_env(*args):
return subprocess.run([py, *args], capture_output=True, text=True).stdout.strip()
print("prefijo del entorno nuevo:", in_env("-c", "import sys; print(sys.prefix)"))
print("paquetes que contiene :", in_env("-m", "pip", "list", "--format=freeze") or "(solo pip)")
print("¿puede importar pandas? :", in_env("-c", "import importlib.util as u; print(u.find_spec('pandas') is not None)"))
print("este kernel tiene pandas :", __import__("importlib.util").util.find_spec("pandas") is not None)
El entorno nuevo está vacío: no ve las bibliotecas instaladas para este notebook. Esa es la idea. En el día a día, desde una terminal:
python3 -m venv .venv # crear (una vez por proyecto)
source .venv/bin/activate # activar (Windows: .venv\Scripts\activate)
pip install -r requirements.txt # instalar las dependencias del proyecto
deactivate # salir
Añade .venv/ a .gitignore: los entornos se reconstruyen a partir de la lista de dependencias y nunca se
suben al repositorio.
3. Gestores de paquetes
| Herramienta | Instala | Úsala para |
|---|---|---|
| pip | paquetes de Python desde PyPI | el estándar; instala en el entorno activo |
| uv | paquetes de Python y el propio Python | un sustituto mucho más rápido de pip/venv/pip-tools, con archivos de bloqueo (uv lock, uv sync) |
| pipx | herramientas de línea de órdenes, cada una en su propio venv oculto | instalar herramientas como ruff, black o httpie sin ensuciar los proyectos |
| conda / mamba | paquetes de Python y que no son de Python (bibliotecas de C/Fortran, CUDA, R) desde conda-forge | entornos científicos con dependencias nativas pesadas, trabajo con GPU |
| Poetry / Hatch / PDM | gestores de proyectos basados en pyproject.toml | resolución de dependencias, archivos de bloqueo, construir y publicar con una sola herramienta |
| apt / dnf (Linux) | paquetes del sistema | el software propio del sistema operativo, compiladores, bibliotecas del sistema |
| snap / flatpak (Linux) | aplicaciones aisladas | aplicaciones de escritorio y herramientas que llevan sus propias dependencias |
| brew (macOS/Linux), winget / choco (Windows) | paquetes del sistema | el mismo papel que apt en otras plataformas |
Buenos hábitos:
- Nunca hagas
sudo pip install: puede romper herramientas de las que depende tu sistema operativo. Usa un venv o pipx. - No mezcles pip y conda en el mismo entorno salvo que sepas por qué; quédate con uno.
- Instala el propio Python con el gestor de paquetes del sistema,
pyenv,uv python installo el instalador oficial.
r = subprocess.run([sys.executable, "-m", "pip", "list", "--format=json"], capture_output=True, text=True)
import json
pkgs = {p["name"].lower(): p["version"] for p in json.loads(r.stdout)}
print(f"{len(pkgs)} paquetes en el entorno de este kernel, por ejemplo:")
for name in ["numpy", "pandas", "polars", "duckdb", "torch", "tensorflow"]:
print(f" {name:<11}{pkgs.get(name, 'no instalado')}")
4. Fijar dependencias
Un proyecto que funcionaba el año pasado puede no instalarse hoy: una dependencia publicó una versión incompatible. Deja constancia de aquello de lo que dependes.
| Archivo | Qué fija | Ejemplo |
|---|---|---|
requirements.txt con rangos |
versiones compatibles | pandas>=2.2,<3 |
requirements.lock / pip freeze |
las versiones exactas que se probaron | pandas==2.2.3 |
pyproject.toml |
las dependencias de un paquete (módulo 07) | dependencies = ["pandas>=2.2"] |
uv.lock, poetry.lock |
versiones exactas más hashes para cada plataforma | los genera la herramienta |
Las bibliotecas declaran rangos (para poder combinarse con otras bibliotecas); las aplicaciones se despliegan con versiones exactas (para que producción ejecute lo que se probó).
frozen = subprocess.run([sys.executable, "-m", "pip", "freeze"], capture_output=True, text=True).stdout.splitlines()
print("\n".join(frozen[:8]), f"\n... {len(frozen)} líneas en total")
5. Contenedores: enviar el entorno, no solo el código
Un entorno virtual aísla los paquetes de Python; un contenedor lo aísla todo: las bibliotecas del sistema operativo, la versión de Python, tu código y su configuración. La misma imagen se ejecuta en tu portátil, en la máquina de un compañero y en un servidor en la nube, por eso la mayoría de los servicios web se despliegan así.
Un Dockerfile para una pequeña API web:
dockerfile = """\
# small official base image with Python
FROM python:3.12-slim
WORKDIR /app
# dependencies first: this layer is cached between builds while only the code changes
COPY requirements.lock .
RUN pip install --no-cache-dir -r requirements.lock
# then the code, which changes often
COPY . .
# do not run as root
USER 1000
EXPOSE 8000
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000"]
"""
compose = """\
services:
api:
build: .
ports: ["127.0.0.1:8000:8000"] # only reachable from this machine (a web server proxies to it)
env_file: .env # secrets stay out of the image
restart: unless-stopped
mem_limit: 512m # a runaway process cannot take the whole server
"""
print(dockerfile); print(compose)
import shutil
docker = shutil.which("docker")
if docker:
print(subprocess.run([docker, "--version"], capture_output=True, text=True).stdout.strip())
else:
print("docker no está instalado aquí (lo normal en Colab). Las órdenes para probar en tu propia máquina:")
print('''
docker build -t my-api . # construir la imagen a partir del Dockerfile
docker run -p 8000:8000 my-api # ejecutar un contenedor
docker compose up -d # ejecutar en segundo plano los servicios descritos en compose.yaml
docker compose logs -f api # seguir los registros
''')
Buenas prácticas con contenedores: parte de una imagen slim; copia e instala las dependencias antes que
el código para que reconstruir sea rápido; ejecuta como usuario sin privilegios de root; guarda los secretos en
variables de entorno o archivos montados al arrancar, nunca dentro de la imagen; fija límites de memoria; un
proceso por contenedor; guarda los datos en volúmenes, no dentro del contenedor.
| entorno virtual | contenedor | |
|---|---|---|
| aísla | paquetes de Python | bibliotecas del sistema, Python, paquetes, configuración |
| tamaño | de decenas a cientos de MB | de cientos de MB a unos pocos GB |
| ideal para | desarrollo, scripts | despliegue, servicios, reproducir un sistema completo |
6. Editores e IDE
| Herramienta | Tipo | Puntos fuertes |
|---|---|---|
| Visual Studio Code | editor gratuito con extensiones | extensiones de Python y Jupyter, depurador, desarrollo remoto por SSH y en contenedores, integración con Git. El más usado hoy. |
| PyCharm | IDE completo (edición Community gratuita) | análisis de código y refactorización profundos, ejecutor de pruebas, herramientas de bases de datos |
| Eclipse + PyDev | IDE (gratuito) | familiar para equipos de Java; depurador, refactorización, soporte de Django |
| Vim / Neovim | editor de terminal | disponible en cualquier servidor y muy rápido una vez aprendido; Neovim con un servidor de lenguaje da funciones de IDE |
| JupyterLab | entorno de notebooks en el navegador | exploración, análisis de datos, docencia (este curso) |
| Spyder | IDE científico | explorador de variables al estilo de MATLAB, popular en ciencia e ingeniería |
| Visual Studio (Windows) | IDE completo | soporte de Python para equipos que ya usan herramientas de Microsoft |
Elijas el que elijas, aprende tres cosas en él: usar el depurador con puntos de interrupción, saltar a la definición y renombrar un símbolo en todo el proyecto. También merece la pena saber lo básico de Vim: a menudo es el único editor disponible en un servidor remoto.
7. Una estructura de proyecto para empezar
mi-proyecto/
├── .venv/ # entorno (no se sube al repositorio)
├── src/mi_proyecto/ # el código, importable como paquete
│ ├── __init__.py
│ └── core.py
├── tests/ # pruebas con pytest (módulo 06)
├── notebooks/ # exploración, no código de producción
├── pyproject.toml # metadatos, dependencias, ajustes de herramientas (módulo 07)
├── requirements.lock # versiones exactas para el despliegue
├── Dockerfile # si se despliega como servicio
├── .gitignore # .venv/, __pycache__/, .env, archivos de datos
├── .env.example # documenta los ajustes; el .env real nunca se sube
└── README.md
Hábitos que compensan: un formateador y un linter (ruff format, ruff check) ejecutados automáticamente con
pre-commit; anotaciones de tipo comprobadas con mypy o pyright; pruebas ejecutadas en cada push por la
integración continua (GitHub Actions); secretos solo en variables de entorno.
Ejercicios
- Crea un venv para un proyecto pequeño, instala
requestsy escribe unrequirements.lockconpip freeze. Borra el venv y reconstrúyelo a partir del archivo de bloqueo. - Instala
ruffconpipxy ejecutaruff checksobre uno de tus scripts. - Escribe un Dockerfile para un script que imprima la fecha actual; constrúyelo y ejecútalo.