virtualenv vs venv vs uv: Python Environments in 2026
Introduction¶
A virtual environment is a directory with its own site-packages and a python
symlink, so one project's dependencies don't collide with another's or with the
system Python. Several tools create them; they differ in speed and in what else
they manage.
What each is¶
venv— standard library (python -m venv). Creates an environment. No dependency resolution beyond pip, no lockfile, no Python-version management. Always available, zero install.virtualenv— the third-party predecessor tovenv. Slightly faster creation, supports older Pythons, a few more options. Rarely needed now.uv— a fast (Rust) all-in-one: creates environments, resolves and installs dependencies, writes a lockfile, managespyproject.toml, and can install Python interpreters itself. A drop-in for pip + venv + pip-tools + pyenv.
The plain venv workflow¶
python -m venv .venv
source .venv/bin/activate # .venv\Scripts\activate on Windows
pip install -r requirements.txt
pip freeze > requirements.txt # crude lock — pins everything, no hashes
deactivate
Works everywhere. Weakness: pip freeze mixes direct and transitive deps and
doesn't separate "what I asked for" from "what got resolved".
Better: pyproject.toml + a real lock¶
pyproject.toml declares your direct dependencies; a lockfile pins the full
resolved tree with hashes.
With uv¶
uv init myproject && cd myproject
uv add requests 'fastapi>=0.110'
uv add --dev pytest ruff
uv run pytest # runs in the managed env, auto-syncing first
uv sync # make .venv match uv.lock exactly
uv lock --upgrade # bump the lock deliberately
uv.lock is committed. uv sync --frozen in CI installs exactly the lock, fast.
uv python install 3.12 grabs an interpreter — no pyenv needed.
With pip-tools (if you can't adopt uv)¶
# requirements.in has your direct deps
pip-compile requirements.in # -> requirements.txt, fully pinned + hashes
pip-sync requirements.txt # make the env match exactly
Reproducibility rules¶
- Commit the lockfile (
uv.lock,poetry.lock, compiledrequirements.txt). - Never commit
.venv/— it's machine-specific;.gitignoreit. - CI installs from the lock, frozen — a CI run should fail if the lock is stale, not silently resolve something new.
- Pin the Python version too (
.python-version, orrequires-python+uv python pin).
Verification and troubleshooting¶
which python; python -c "import sys; print(sys.prefix)" # is the venv active?
pip --version # which pip
python -m pip list
uv pip list
pip installhit the system Python — venv not activated, or an IDE using a different interpreter. Checksys.prefix.- "works on my machine" — no lockfile, or CI didn't install
--frozen. Someone's transitive dep floated to a new version. source: no such fileon activate — env created with a different Python that's since moved, or you're in the wrong shell (activate.fish,Activate.ps1). Recreate:rm -rf .venv && uv venv/python -m venv .venv.- Slow installs in CI — no wheel cache. uv caches aggressively by default;
for pip, cache
~/.cache/pip(see GitHub Actions cache strategies). uvand an existing.venvdisagree —uv syncreconciles to the lock; if you hand-pip installed into it, uv will remove the extras.- Hash mismatch on install — the lockfile hashes don't match the downloaded wheel (mirror issue, or a re-uploaded package). Re-lock.
- Multiple Python versions confusion —
uv run --python 3.11 ...orpy -3.12 -m venv(Windows launcher) to be explicit.
Related tools and reading¶
- On-site: Linux commands reference.
- Related posts: Packaging a Python CLI, argparse patterns for CLI tools.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Python
asyncio for I/O-Bound Scripts
Async Python shines for hundreds of concurrent network calls and nothing else. Here's a correct pattern for a bulk-fetch script, with concurrency limits and error handling.
October 24, 2026Python Type Hints That Actually Pay Off
Type hints earn their keep at function boundaries and data models. Here's a pragmatic subset, a CI setup, and how to adopt them on an untyped codebase.
October 24, 2026Packaging a Python CLI
Turn a script into a `pip install`-able command: the pyproject.toml, the entry point, the src layout, and how users should install it.
October 24, 2026