PythonIntermediate

Python Virtual Environments: venv, pipenv, poetry, or uv?

A comparison of Python's dependency management tools and a practical recommendation for which one to use in 2026.

DevFieldGuideJuly 30, 2026 (updated July 31, 2026)6 min read
Share:

Every Python project needs isolated dependencies, but the ecosystem has accumulated four competing tools for it. Here's how they actually compare.

venv — the built-in baseline

bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

venv ships with Python itself. It's the lowest common denominator: no lockfile, no dependency resolution beyond pip's, but zero extra install required. Good for small scripts and quick experiments.

pipenv — venv plus a lockfile

bash
pipenv install requests
pipenv shell

Adds a Pipfile.lock for reproducible installs and merges virtual env management with dependency management. Largely superseded by poetry and uv in new projects, but still common in legacy codebases.

poetry — dependency management and packaging

bash
poetry init
poetry add requests
poetry run python main.py

Poetry handles dependency resolution, lockfiles, and package publishing in one tool. It's a solid default for libraries you intend to publish to PyPI.

uv — the fast newcomer

bash
uv venv
uv pip install requests

uv (from the makers of Ruff) reimplements the pip/venv workflow in Rust and is often 10-100x faster for dependency resolution and installs. It's rapidly becoming the default recommendation for new projects in 2026.

Recommendation

SituationUse
Quick script, no teamvenv + pip
Publishing a library to PyPIpoetry
New project, want speeduv
Maintaining a legacy projectWhatever it already uses

If you're starting a new project today with no constraints, uv is the pragmatic default — it's compatible with existing requirements.txt and pyproject.toml conventions, so switching later isn't a rewrite.

Prerequisites
  • Python 3.9+ installed and available on your PATH
  • Basic familiarity with the terminal and pip
  • For uv: no separate install of Python required — uv can install and manage Python versions itself

Once your environment is isolated and reproducible, the same discipline pays off directly the moment you start writing async Python with asyncio — a clean, pinned dependency set is one less variable when debugging why concurrent code behaves differently than expected.

What actually happens when you "activate" a venv

source .venv/bin/activate doesn't install anything or change files on disk — it modifies your current shell session's PATH (and a couple of related environment variables) so that python and pip resolve to the binaries inside .venv instead of the system ones:

bash
which python
# before activation: /usr/bin/python3
# after activation:  /path/to/project/.venv/bin/python

This is why activation is per-shell-session, not global or persistent — opening a new terminal tab starts fresh with the system Python again, and needs its own activate call. It's also why "activating" doesn't require any special permissions or system-level changes: it's a shell-local PATH change, nothing more, which is exactly what makes venvs cheap enough to create one per project without worrying about a limited system-wide resource.

Reproducing an environment on a new machine

The lockfile each tool generates is what turns "works on my machine" into "works on every machine" — installing from it, rather than a loosely-versioned requirements.txt, pins exact versions of every transitive dependency, not just the ones you explicitly listed:

bash
# venv/pip
pip install -r requirements.txt
 
# poetry
poetry install
 
# uv
uv sync

The distinction that matters: a plain requirements.txt with unpinned or loosely-pinned versions (requests>=2.0) can resolve to different actual versions on different days as new releases publish — a real lockfile (poetry.lock, uv.lock, or a pip freeze-generated requirements.txt) pins exact versions for every package in the dependency tree, guaranteeing the same install every time regardless of when it runs.

uv's Python version management

Beyond package installation speed, uv can also install and manage Python interpreter versions themselves, similar to what pyenv does separately for the other tools:

bash
uv python install 3.12
uv venv --python 3.12

This collapses two previously separate concerns (which Python interpreter, which packages) into one tool, which is part of why it's become the default recommendation for new projects — one install, one tool, one lockfile format, instead of coordinating pyenv and a separate package manager together.

Common mistakes

Common mistakes
  • Installing packages with a global pip install before activating a virtual environment — this silently pollutes your system Python instead of the project's isolated one. Always confirm which python (or where python on Windows) points inside .venv before installing anything.
  • Committing the .venv/venv folder to git. It's large, machine-specific, and unnecessary — commit the lockfile instead, and add the environment folder to .gitignore.
  • Running two isolation tools at once (e.g. activating a venv inside an already-active conda environment). Package resolution gets confused about which environment actually owns a given install.
  • Pinning exact versions in requirements.txt by hand instead of generating it from an actual resolved environment (pip freeze > requirements.txt) — hand-written pins drift from what's actually installed and reintroduce the reproducibility problem venv was meant to solve.

Troubleshooting

Best practices
  • "command not found: python" — many systems (especially Linux) only expose python3, not python, for the system interpreter. Use python3 -m venv .venv, or alias it if your workflow expects python.
  • Wrong Python version inside the venv — a virtual environment inherits whichever interpreter created it. If python -m venv .venv picks up an unexpected version, check pyenv versions (if you use pyenv) or specify the interpreter explicitly: python3.12 -m venv .venv.
  • ModuleNotFoundError right after installing a package — almost always means the venv isn't actually activated, or you have a second venv shadowing it. Check your shell prompt for the (.venv) prefix, and confirm with which python.
  • uv or poetry command not found after install — both install to a user-local bin directory that may not be on your PATH by default; their installers print the exact line to add, and it's worth actually reading that output rather than skipping past it.
Advertisement

Frequently Asked Questions

Advertisement
DevFieldGuide
DevFieldGuide

Editorial Team

Practical tutorials and developer tools, written and maintained by the DevFieldGuide team.

Enjoyed this article?

Get the next one straight to your inbox, along with the best of what we publish each week.

Related Articles

More in Python

View all