Instala dos proyectos de Python en la misma máquina y el segundo puede romper el primero. Uno necesita requests==2.28, el otro necesita requests==2.32, y pip tiene exactamente un directorio site-packages global en el que escribir. Gana la última instalación, en silencio. Te enteras cuando el otro proyecto empieza a lanzar errores que no tienen nada que ver con el código que acabas de cambiar.

Un entorno virtual le da a cada proyecto su propio site-packages. No se toca nada global, y dos proyectos pueden depender de versiones incompatibles de la misma librería sin que ninguno se entere.

¿Qué es un entorno virtual de Python?

Un venv es un directorio con tres cosas: un intérprete de Python (un symlink al del sistema en Linux y macOS), un archivo pyvenv.cfg que apunta a esa instalación base, y una carpeta site-packages donde pip install escribe los paquetes. Activarlo pone el directorio bin del venv (Scripts en Windows) al principio del PATH de tu shell, así python y pip resuelven a las copias del venv en lugar de las del sistema.

Ese es todo el mecanismo. No hay aislamiento de filesystem ni una segunda copia de Python. Un venv solo cambia dónde encuentra la shell dos binarios y desde dónde se resuelven los imports, por eso crear uno tarda un segundo y borrar uno es rm -rf .venv. Si necesitas aislar también el sistema operativo y el filesystem, eso es trabajo de un contenedor, no de un venv.

¿Cómo creas y activas un venv?

La librería estándar incluye el módulo venv, así que no hay nada que instalar:

1
python -m venv .venv

.venv es el nombre convencional, y uv y la mayoría de los editores lo buscan por defecto. La activación depende de tu shell:

OS / shellComando
Linux / macOS (bash, zsh)source .venv/bin/activate
Windows (cmd.exe).venv\Scripts\activate.bat
Windows (PowerShell).venv\Scripts\Activate.ps1

Una instalación nueva de Windows bloquea los scripts de PowerShell, incluido el de activación. Permite los scripts creados localmente una vez por usuario:

1
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Con el venv activo, tu prompt muestra el prefijo (.venv) y which python (where python en Windows) apunta dentro de .venv. Ejecuta deactivate para volver atrás. Es una función de shell definida por el script de activación, no un programa aparte.

La activación es opcional. .venv/bin/python script.py y .venv/bin/pip install requests funcionan sin ella, y así es como las pipelines de CI y los editores llaman al venv bajo el capó.

Con más de una versión de Python instalada, python -m venv usa el python que la shell encuentre primero, que muchas veces no es el que querías. Especifica la versión: python3.12 -m venv .venv en Linux y macOS. En Windows, usa el launcher py que viene con Python, que elige una versión por número sin importar el PATH: py -3.12 -m venv .venv.

¿Cómo instalas paquetes con pip y requirements.txt?

Dentro de un venv activo, pip install <package> hace lo que esperas. Para la reproducibilidad, lo que importa es registrar qué se instaló:

1
2
pip install requests flask
pip freeze > requirements.txt

pip freeze escribe cada paquete instalado con su versión exacta, incluidas las dependencias de tus dependencias. Haz commit de requirements.txt, y cualquiera que clone el proyecto ejecuta:

1
2
3
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

y obtiene las versiones que tú tenías. El punto débil: el archivo mezcla lo que pediste tú con todo lo que pip trajo de paso. Seis meses después, distinguir “necesito Flask” de “Flask necesitaba Werkzeug 3.1.3” significa leer toda la lista y adivinar.

pyproject.toml frente a requirements.txt: ¿cuál es la diferencia?

pyproject.toml registra solo tus dependencias directas, con rangos de versión:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[project]
name = "my-project"
version = "0.1.0"
dependencies = [
    "requests>=2.31",
    "flask>=3.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=8.0",
    "ruff",
]

pip install . lee esto y resuelve todo el árbol de dependencias al instalar. pip install ".[dev]" añade encima el grupo dev. El mismo archivo guarda los metadatos de empaquetado si algún día publicas el proyecto, algo para lo que requirements.txt nunca se diseñó.

Lo que no hace es fijar versiones exactas. flask>=3.0 resuelve a la más reciente que exista el día que instalas. Fijar versiones es trabajo de un lock file, y venv + pip no te dan uno.

En una build de contenedor, este es el archivo que copias antes del resto del código fuente, así la capa de dependencias solo se reconstruye cuando cambia pyproject.toml. La guía de buenas prácticas de Dockerfile explica por qué ese orden importa para la caché de build.

¿Cómo usas uv en lugar de venv y pip?

uv es un único binario en Rust que cubre el trabajo de venv, pip, pip-tools y pyenv: uv python install 3.12 descarga y gestiona versiones de Python igual que pyenv, así que uv venv --python 3.12 funciona aunque esa versión no esté todavía en la máquina. Además es lo bastante rápido como para notarlo: una resolución que a pip le toma varios segundos termina en una fracción de ese tiempo.

Instálalo una vez (la documentación de astral.sh lista el comando para cada plataforma). Después puedes usarlo de dos formas.

Como reemplazo directo de venv y pip, si solo quieres velocidad:

1
2
uv venv
uv pip install requests flask

uv venv crea .venv igual que python -m venv .venv, y uv pip install acepta los mismos argumentos que pip. El resultado se activa como cualquier otro venv.

En modo proyecto, que añade locking:

1
2
3
4
uv init my-project
cd my-project
uv add requests flask
uv add --dev pytest

uv init crea la estructura de un pyproject.toml. uv add escribe la dependencia ahí, resuelve todo el árbol y escribe un archivo uv.lock que fija cada versión resuelta, incluidas las transitivas, con hashes. Ese lock file es la pieza que pip no produce. Haz commit de pyproject.toml y de uv.lock, y un compañero ejecuta:

1
uv sync

Esto crea .venv si falta e instala exactamente lo que dice el lock file, no lo que los rangos de versión en pyproject.toml resuelvan ese día. uv run script.py ejecuta la misma comprobación de sincronización antes de correr, así tu entorno no puede alejarse del lock file sin que lo notes.

¿Cómo arreglas los errores más comunes de los entornos virtuales?

error: externally-managed-environment. Ejecutaste pip install fuera de un venv en Debian 12+, Ubuntu 23.04+, Fedora 38+ o una distro parecida, y el Python del sistema se negó. PEP 668 introdujo un archivo marcador que el gestor de paquetes del sistema operativo coloca en su instalación de Python para impedir que pip sobrescriba paquetes que también gestionan apt o dnf. Antes de esto, un sudo pip install suelto podía romper las propias herramientas del sistema operativo de formas horribles de depurar. Algunas guías antiguas te dicen que añadas --break-system-packages. No lo hagas: trae de vuelta exactamente el destrozo que el marcador evita. Crea un venv e instala ahí.

El editor ejecuta el intérprete equivocado. VS Code, PyCharm y herramientas similares usan el python que detectaron al abrir el proyecto, que no es necesariamente el de tu venv. Los síntomas: imports que funcionan en la terminal aparecen como no resueltos en el editor, o un paquete que instalaste hace cinco minutos no se encuentra. Configura explícitamente el intérprete del editor a .venv/bin/python (.venv\Scripts\python.exe en Windows).

El venv se subió a git. Son unos cientos de megabytes de binarios más rutas absolutas en pyvenv.cfg y en los scripts de activación, así que se rompe en cuanto alguien clona el repo en una ruta o sistema operativo distinto. Añade .venv/ a .gitignore. Desde Python 3.13, venv también escribe un .gitignore dentro del nuevo entorno que ignora su contenido, pero en versiones anteriores tienes que hacerlo tú. Haz commit de requirements.txt, o de pyproject.toml con uv.lock. Esos archivos reconstruyen el entorno en cualquier parte. El directorio en sí nunca pertenece al repo.

venv, pyproject.toml o uv: ¿cuál deberías usar?

Quédate con venv + pip + requirements.txt para un proyecto pequeño con pocas dependencias donde no quieres tooling extra. Pasa a pyproject.toml en cuanto tengas dependencias solo de desarrollo como test runners y linters que no deben llegar a producción, o cuando planees empaquetar el proyecto.

Añade uv cuando la velocidad de resolución empiece a doler o quieras un lock file versionado sin mantener uno a mano con pip-tools. No te obliga a reescribir nada: ejecuta uv init en un proyecto nuevo, o uv venv y uv pip install -r requirements.txt en uno existente, y pasa a uv add cuando estés listo para el lock file.

Artículos relacionados