Python 1001

Inicial · Notebook 04 de 13

Entornos, paquetes, contenedores y editores

Entornos aislados, pip, uv, pipx, conda, apt y snap, archivos de bloqueo, Docker para desplegar y los editores que usan los profesionales: VS Code, PyCharm, Eclipse, Vim.

  • venv
  • pip y uv
  • conda
  • archivos de bloqueo
  • Docker
  • IDE
⏱ ~60 min 💻 se ejecuta en 15 s en una CPU ⬇ Descargar notebook Abrir en Colab Ver en GitHub

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

  1. Por qué cada proyecto necesita su propio entorno y cómo funciona venv (demostrado en vivo)
  2. Gestores de paquetes comparados: pip, uv, pipx, conda/mamba y los del sistema (apt, snap, brew, winget)
  3. Fijar dependencias: archivos de requisitos, archivos de bloqueo y pyproject.toml
  4. Docker: empaquetar una aplicación con todo su entorno y desplegarla
  5. Editores e IDE: VS Code, PyCharm, Eclipse con PyDev, Vim/Neovim, JupyterLab, Spyder
  6. 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)
intérprete  : /home/patito/Developments/academy/python/.venv/bin/python3
version     : 3.12.3
prefijo     : /home/patito/Developments/academy/python/.venv
prefijo base: /usr
en un venv  : True

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)
prefijo del entorno nuevo: /tmp/tmpmkd0jkxb/demo-env
paquetes que contiene    : pip==24.0
¿puede importar pandas?  : False
este kernel tiene pandas : True

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 install o 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')}")
184 paquetes en el entorno de este kernel, por ejemplo:
  numpy      2.5.4
  pandas     2.3.3
  polars     2.0.0
  duckdb     1.5.6
  torch      2.14.1+cpu
  tensorflow 2.21.0

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")
absl-py==2.5.1
anyio==4.15.1
argon2-cffi==25.1.0
argon2-cffi-bindings==26.1.0
arrow==1.4.0
astropy==8.0.2
astropy-iers-data==0.2026.10.12.1.3.27
asttokens==3.0.2 
... 183 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)
# 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"]

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

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
''')
Docker version 29.8.1, build 4a63305

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

  1. Crea un venv para un proyecto pequeño, instala requests y escribe un requirements.lock con pip freeze. Borra el venv y reconstrúyelo a partir del archivo de bloqueo.
  2. Instala ruff con pipx y ejecuta ruff check sobre uno de tus scripts.
  3. Escribe un Dockerfile para un script que imprima la fecha actual; constrúyelo y ejecútalo.