Skip to main content
Python Tooling2026-07-31 · 9 min read

Python Virtual Environments in 2026: venv, uv, pipx, poetry — When to Use Which

Every Python tutorial says "use a virtual environment" and moves on. This one goes the extra mile: what a venv actually IS, the four tools worth knowing in 2026, a 60-second decision tree for picking the right one, and the two mistakes that break your setup within a week.

What a virtual environment actually is

A venv is a directory containing a Python interpreter symlink plus a private `site-packages/`. When it's "activated", PATH prepends <venv>/bin, so python and pip in your shell point at the venv's copies. pip install X writes to the venv's site-packages/, not the system Python's.

That's it. No magic. It's a folder with a symlink and a pyvenv.cfg.

BASH
$ python -m venv .venv $ ls .venv/ bin/ include/ lib/ pyvenv.cfg $ cat .venv/pyvenv.cfg home = /opt/homebrew/opt/python@3.13/bin include-system-site-packages = false version = 3.13.2

Everything else (activate scripts, wrappers, package managers) is convenience on top of this shape.

Why you need one, always

Without a venv, pip install X writes to your system Python's site-packages/. Three problems:

1. Conflicts. Project A needs django==4.2; Project B needs django==5.1. Impossible without venvs.

2. Reproducibility. "Works on my machine" is a system-Python problem. A venv snapshot IS the environment.

3. Cleanup. Delete a project? Delete its venv folder. System-Python installs accumulate forever.

On macOS 12+ and most Linux distros, system-Python is now owned by the OS — pip can even refuse to install without --break-system-packages. That's a hint.

The 4 tools worth knowing

1. venv — the stdlib baseline

Ships with every Python 3.3+ install. Zero dependencies. The universal fallback:

BASH
python -m venv .venv # create source .venv/bin/activate # macOS / Linux .venv\Scripts\activate # Windows pip install -r requirements.txt deactivate # or close the shell

Use when: writing a tutorial, ensuring a script works everywhere, or in a locked-down CI where you can't install anything.

2. uv — the modern default (2026)

Rust-written, 10-100x faster than pip. Replaces venv + pip + pip-tools + pyenv-download-python. See our uv deep-dive for the full tour.

BASH
uv venv # create venv (uses current Python) uv venv --python 3.13 # or download + pin a version uv pip install -r requirements.txt uv run pytest # run without activating uv add fastapi # append to pyproject.toml + install uv sync # install exactly what pyproject.toml says

Use when: any 2026 project — solo, team, CI, Docker. The speed difference on a cold cache is real (45s pip → 2s uv on a typical Django project).

3. pipx — for CLI tools

When you install a CLI like ruff, httpie, black, youtube-dl, mypy, you don't want it in your project venv (why depend on ruff for import project.things to work?). And you don't want it in system Python (conflicts). pipx puts each in its own isolated venv but exposes the CLI globally:

BASH
pipx install ruff pipx install httpie pipx install black ruff check . # works from anywhere, no activation httpie GET api.github.com black .

Use when: installing a Python-based CLI. Rule of thumb: pipx install X if X ships an executable you'd run from any project; uv pip install X if X is a library you import.

Modern replacement: uv tool install ruff does the same thing without a separate pipx install. Pick either.

4. poetry — mature dependency-locker (declining share)

Before uv landed, poetry was the go-to modern packaging tool. Excellent dependency resolver, deterministic poetry.lock, tight integration with pyproject.toml. Still solid — but slower than uv and adds cognitive overhead.

BASH
poetry new my-project poetry add fastapi poetry install poetry run pytest poetry publish

Use when: existing project already uses poetry, or you need poetry-specific plugins (e.g. poetry-plugin-export). For NEW projects in 2026, uv gets you there faster with a smaller footprint.

The 60-second decision tree

▶ PYTHONeditable · runs in your browser

That's it. 90% of Python developers today can pick from this list without ever needing pyenv, virtualenvwrapper, pew, or conda.

Conda — the special case

conda is a separate universe: package manager + venv manager + Python distributor + C-library manager, primarily for scientific-Python. If you're doing heavy numerical work with numpy / scipy / pandas / GPU stacks that need matched CUDA + cuDNN versions, miniconda may still be worth it — the pre-compiled binaries save hours vs pip.

Otherwise: plain uv is now competitive on scientific stacks too (numpy / pandas installs are seconds, not minutes). Don't reach for conda unless you specifically need a C-library binding pip can't easily produce.

The two mistakes that break your setup within a week

1. Committing .venv/ to git

A venv contains:

  • Absolute paths to your specific Python install.
  • Compiled .pyc files.
  • Binaries that are OS-specific.

Commit that → your teammate on Windows can't run anything. Add .venv/ (and venv/, .env/) to your .gitignore FIRST, then create the venv.

2. Activating in the wrong shell

source .venv/bin/activate sets env vars in the CURRENT shell only. If you open a new terminal tab, you're back to system Python. Two safe patterns:

1. Reactivate every session — the standard flow.

2. `uv run` / `poetry run` / `python -m project` — never activate at all. Run commands from a fresh shell prefixed with the tool. Great in Makefiles, CI, npm-scripts, git hooks.

Rule for scripts: if it's a one-liner run from CI or a Makefile, DON'T activate — use uv run pytest or the full path .venv/bin/pytest. Activation is for interactive shells only.

Migration between tools

pip + venv → uv:

BASH
rm -rf .venv uv venv uv pip install -r requirements.txt

Requirements.txt keeps working verbatim. Two minutes.

poetry → uv:

BASH
uvx migrate-to-uv # community tool, converts pyproject.toml

venv → poetry:

BASH
poetry init poetry add $(pip freeze | cut -d= -f1)

Verification

After setup, always check WHICH Python + pip you're using:

BASH
$ which python /path/to/project/.venv/bin/python # ✅ venv /opt/homebrew/bin/python # ❌ system, activation didn't take $ which pip /path/to/project/.venv/bin/pip # ✅ venv $ python -c "import sys; print(sys.prefix)" /path/to/project/.venv # ✅ venv

If which python shows a system path after activation, something's off — usually you activated in a subshell that already exited.


Virtual environments are one of the few Python topics where the modern answer (2026) is genuinely simpler than the historical answer. uv for new projects, venv for scripts, pipx for CLIs. Done.

Next step: the Python DevOps track covers uv + ruff + Docker + the modern Python deployment stack — hands-on with real Python running in your browser.

Ready to try it?

15 lessons free after a free signup — no card. Sample the platform, then decide.

Start free

Or unlock every lesson

Pro — $12/mo, unlimited AI, cancel any time. Refund within 14 days worldwide.

See Pro plans

Get one Python lesson + one career idea every Friday

No spam, no "buy our course now". Three bullets, every Friday. Unsubscribe with one click.