A virtual environment is a self-contained directory with its own Python
interpreter and its own site-packages, so each project gets an isolated set of
dependencies. Isolation matters because two projects often need different
versions of the same package — without venvs, installing for one breaks the other,
and installing globally can even break system tools.
# Project A needs Django 3, Project B needs Django 5.
# Separate venvs let both coexist without conflict.
python -m venv .venv # creates an isolated environment
A venv keeps dependencies per project and out of the global interpreter, making builds reproducible and avoiding "works on my machine" version clashes. Use one for every project.
Create one with the standard-library venv module, then activate it so the
shell uses the venv's python and pip. The activation command differs by OS;
deactivate returns to the global interpreter.
python -m venv .venv # create (folder named .venv)
source .venv/bin/activate # activate on macOS / Linux
.venv\Scripts\activate # activate on Windows
which python # -> .../.venv/bin/python while active
deactivate # leave the venv
Once active, pip install puts packages inside the venv only. Add .venv/ to
.gitignore — you commit the dependency list, not the environment itself.
pip install fetches packages from PyPI into the active environment. A
requirements.txt lists a project's dependencies (often with pinned versions)
so anyone can recreate the same environment with one command.
pip install requests # install latest
pip install "requests==2.31.0" # install a specific version
pip install -r requirements.txt # install everything listed in the file
# requirements.txt
requests==2.31.0
rich>=13.0
Committing requirements.txt makes installs reproducible across machines and
CI. Install into an activated venv, never globally with sudo pip.
pip freeze prints every installed package with its exact pinned version in
requirements.txt format, so you can capture the current environment. Redirect it
to a file to snapshot dependencies.
pip freeze # list installed pkgs == versions
pip freeze > requirements.txt # snapshot the current environment
pip list # similar, but human-readable table
A caveat: pip freeze records everything installed, including transitive
dependencies, which can make the file noisy. Many teams instead hand-curate
direct dependencies (or use a lock-file tool) and keep pip freeze for capturing a
known-good full snapshot.
An editable install links your project into the environment in place instead of copying it, so edits to the source take effect immediately without reinstalling. It's the standard way to work on a package you're developing locally.
pip install -e . # install the current project, editable
pip install -e ".[dev]" # editable + optional 'dev' extras
Because the install points at your working tree, changing the code updates the
imported package right away — no rebuild needed. Use -e for your own package
under development, and a normal pip install for third-party dependencies.
pyproject.toml is the modern, standardized config file (PEP 518/621) for a
Python project — it declares the build system, project metadata, and
dependencies in one place, replacing the older setup.py/setup.cfg split. Most
modern tools (build, pip, linters, formatters) read it.
[project]
name = "myapp"
version = "0.1.0"
dependencies = ["requests>=2.31", "rich"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
With dependencies declared here, pip install . (or -e .) reads them directly, so
a separate requirements.txt becomes optional. It's the recommended starting point
for any new packaged project.
Activation prepends the venv's bin/Scripts directory to PATH and sets
VIRTUAL_ENV, so python and pip resolve to the venv's copies first. The venv's
pyvenv.cfg points back to the base interpreter; there's no magic beyond PATH.
source .venv/bin/activate
echo $PATH # .venv/bin is now first
which python # .../.venv/bin/python
echo $VIRTUAL_ENV # path to the venv
Rule of thumb: a venv is mostly a PATH trick + isolated site-packages; you can
even skip activation by calling .venv/bin/python directly.
venv is built into the stdlib and manages Python packages for one Python
version. virtualenv is a faster third-party superset (supports older Pythons,
more features). conda is a separate ecosystem that also manages non-Python
binaries and Python itself.
python -m venv .venv # stdlib, simplest
virtualenv .venv # third-party, more features
conda create -n myenv python=3.12 numpy # manages Python + native deps
Rule of thumb: use venv for most projects; conda when you need complex native/
scientific binaries or to manage the Python version itself.
Specifiers constrain acceptable versions: == exact, >= minimum, ~=
"compatible release" (allows the last digit to grow), and != to exclude. They
balance reproducibility against getting fixes.
requests==2.31.0 # exactly this version
requests>=2.28 # this or newer
requests~=2.31.0 # >=2.31.0, <2.32.0 (compatible release)
requests>=2.0,<3.0 # range
Rule of thumb: pin exact (==) versions in a lock/deploy file for reproducibility;
use ranges (>=, ~=) for libraries to stay compatible with users' other deps.
pipx installs Python applications (CLI tools) each into their own isolated
venv while exposing the command globally — avoiding dependency clashes in your base
environment. Use pip for libraries your project imports.
pipx install black # global `black` command, isolated deps
pipx install httpie
pip install requests # a library to import in your code
Rule of thumb: pipx for standalone tools you run (linters, formatters, CLIs);
pip (inside a venv) for packages you import.
Pip's resolver finds a set of versions satisfying all constraints (direct and
transitive); it errors on conflicts. Because requirements.txt may not pin
everything, lock files (pip-tools, Poetry, uv) record the exact resolved
versions for fully reproducible installs.
pip install -r requirements.txt # resolver picks compatible versions
pip-compile requirements.in # -> pinned requirements.txt (lock)
uv lock # modern lock-file workflow
Rule of thumb: use a lock file (compiled pins) for apps/deploys so every install is identical; loose ranges are for libraries that must coexist with others.
Installing globally as root can overwrite or break system packages that the OS
depends on, and mixes project deps into the system Python. Use a venv (or
pip install --user) instead so installs stay isolated and reversible.
sudo pip install foo # risky: can clobber system Python packages
python -m venv .venv && source .venv/bin/activate && pip install foo # safe
Rule of thumb: never sudo pip; isolate with a venv. Modern Linux even blocks global
pip installs (PEP 668 "externally-managed-environment") to prevent this.
Use pip install --upgrade (or -U) to update, and pip uninstall to
remove. pip show inspects a package; pip list --outdated finds upgradable ones.
pip install --upgrade requests # update to the latest allowed
pip install -U pip # upgrade pip itself
pip uninstall requests # remove (asks to confirm)
pip list --outdated # what can be upgraded
pip show requests # version, location, dependencies
Rule of thumb: pip uninstall doesn't remove a package's dependencies — use a
lock/clean reinstall or a tool like pip-autoremove to prune orphans.
Put abstract dependencies (ranges) in pyproject.toml for a library/package
others install. Use requirements.txt (often pinned) for an application/
deployment environment you control. They serve different audiences.
# pyproject.toml — library deps, loose
dependencies = ["requests>=2.28", "click"]
# requirements.txt — app deploy, pinned
requests==2.31.0
click==8.1.7
Rule of thumb: pyproject.toml declares what your package needs; requirements.txt
(or a lock file) records the exact environment to reproduce for an app.
Pip caches downloaded wheels so repeat installs are fast and don't re-download. You
can build a local wheelhouse with pip download and install from it offline with
--no-index --find-links.
pip cache dir # where wheels are cached
pip install --no-cache-dir foo # bypass the cache
pip download -r requirements.txt -d wheels/ # fetch wheels for offline use
pip install --no-index --find-links=wheels/ -r requirements.txt
Rule of thumb: rely on the cache for speed; use pip download + --find-links to
install on air-gapped/offline machines reproducibly.
More Modules, Packages & Environments interview questions
More ways to practice
The self-quiz is live. Join our channel for updates, new content & tech tips.