You installed a package for one project and now a completely different project crashes. Sound familiar? Congratulations, you’ve just discovered why virtual environments exist.
The problem in 30 seconds
Python has one global site-packages folder where pip install dumps everything. If Project A needs pandas 1.5 and Project B needs pandas 2.1, you have a fight. The last one installed wins, and the other project breaks. Silently, of course — Python is polite like that.
A virtual environment gives each project its own isolated site-packages folder. Project A gets its pandas 1.5, Project B gets its pandas 2.1, and they never know about each other. Problem solved.
The built-in way: venv
Python ships with venv since version 3.3. No installation needed.
# Create a virtual environment in a folder called .venv
python -m venv .venv
# Activate it (Linux/macOS)
source .venv/bin/activate
# Activate it (Windows)
.venv\Scripts\activate
# Now pip install goes into .venv, not your system Python
pip install pandas==2.1.0
When you’re done, deactivate to go back to your system Python. The .venv folder is just a regular directory — delete it and the environment is gone.
Things people forget
- Add
.venv/to.gitignore. You don’t commit the environment. You commit arequirements.txt(orpyproject.toml) and recreate it wherever you deploy. - Always freeze your dependencies.
pip freeze > requirements.txtsaves the exact versions you have installed. Without this,pip installgrabs the latest version and things might break in production. - One environment per project. Don’t share environments between projects unless you want to relive the exact problem you were trying to solve.
Poetry: the grown-up option
venv + pip + requirements.txt works but has sharp edges. Poetry wraps the whole workflow into one tool:
# Install poetry (once)
pip install poetry
# Start a new project
poetry init
# Add a dependency
poetry add pandas
# Install from the lock file (exact versions, reproducible)
poetry install
Poetry generates a pyproject.toml (your requirements) and a poetry.lock (pinned versions of every dependency, including sub-dependencies). The lock file is the thing that makes builds reproducible — commit it.
Poetry creates and manages the virtual environment for you automatically. You never need to activate it manually; poetry run python script.py runs inside the environment.
When to use it
- Projects with more than a handful of dependencies
- Anything that ships to production or gets shared with a team
- When you’re tired of manually syncing
requirements.txt
uv: the new fast kid
uv is a relatively new tool (by the Astral team, the same people behind ruff) that replaces pip, pip-tools, virtualenv, and parts of Poetry in a single Rust binary. It’s fast — 10–100× faster than pip for resolution and installation.
# Install uv
pip install uv
# Create a venv
uv venv
# Install from requirements
uv pip install -r requirements.txt
# Or manage a project (poetry-like workflow)
uv init
uv add pandas
uv sync
uv is still maturing, but it’s already excellent for:
- Fast dependency installation in CI pipelines
- Projects where you want one tool to handle environments + packages
- Anyone who’s tired of waiting 30 seconds for pip to resolve a dependency tree
Which one should you use?
| You need… | Use |
|---|---|
| Something that just works, no install | venv + pip |
| Reproducible builds + dependency management | Poetry |
| Speed above all, bleeding edge OK | uv |
| A data science notebook, path of least resistance | conda |
If you’re just starting out, venv + pip is fine. Learn how environments work at the fundamental level before adding abstractions on top. Once you feel the pain of manually managing requirements.txt across multiple projects, Poetry is the natural next step.
The important thing isn’t which tool. It’s that you use one of them. Running pip install into your global Python is how you end up reinstalling everything from scratch every six months.
The one-sentence rule
Every Python project gets its own virtual environment, always, no exceptions, even if it’s a tiny script. The three minutes it takes to set up saves three hours of debugging dependency conflicts later.