Beginner · Lesson 32 of 94

Virtual environments, pip, and uv

Watch this lesson

View full playlist ↗

Read the lesson

What you'll learn

Concept

Third-party packages are code other people wrote that you install with pip and then import: a web framework, an HTTP client, a testing tool. They come from the Python Package Index, PyPI, and there are half a million of them. Installing them into the Python that came with your operating system is a mistake you make once: two projects need different versions of the same package, and one of them breaks.

A virtual environment solves this. It is a folder, usually called .venv inside the project, containing a private copy of the Python launcher and its own site-packages where installed packages live. Activate it and python and pip point at that folder; deactivate it and they point back at the system. Each project gets its own, so their dependencies never meet.

Virtual environments, pip, and uv: system Python 3.14 → project-a/.venv; httpx 0.28 → project-b/.venv; httpx 0.21, pytest 9 → project-c/.venv; nothing yet
Virtual environments, pip, and uv · Figure 1 · Open for full size

The workflow is three commands. python -m venv .venv creates the environment. Activating it is source .venv/bin/activate on macOS and Linux or .venv\Scripts\activate on Windows, and your prompt changes to show the name. pip install httpx then installs into it. pip list shows what is there, and pip install --upgrade or pip uninstall do what they say. Never commit .venv to git; it is rebuilt from the requirements file.

Command Does
python -m venv .venv create an environment in .venv
source .venv/bin/activate use it (Windows: .venv\Scripts\activate)
pip install httpx==0.28.1 install one package at an exact version
pip freeze > requirements.txt record everything installed, with versions
pip install -r requirements.txt install exactly that list somewhere else
deactivate go back to the system Python

Pinning means writing the exact version, httpx==0.28.1, rather than the bare name httpx. Without a pin, the same pip install gives a different version next month, and a project that worked stops working for no visible reason. pip freeze writes every installed package with its exact version into requirements.txt; a teammate, a server, or you in a year runs pip install -r requirements.txt and gets the identical set. This is the single habit that separates code that runs on your machine from code that runs anywhere.

uv is a newer tool that replaces venv and pip with one fast command and adds what they lack: it downloads Python versions for you, it keeps dependencies in pyproject.toml, and it writes a lockfile that records the complete resolved set. uv init, uv add httpx, and uv run python main.py cover the daily loop, and uv sync rebuilds an environment from the lockfile in seconds. This course's own tooling runs on it. Learn pip because every tutorial uses it; use uv for your own projects from the Expert level on.

Virtual environments, pip, and uv: uv init → uv add httpx → pyproject.toml + uv.lock updated → uv run python main.py → teammate: uv sync → identical environment
Virtual environments, pip, and uv · Figure 2 · Open for full size

Code

Where the running Python lives, and whether it is inside a virtual environment. This course's snippets run inside one, which the output reflects.

# where_am_i.py
import sys
from pathlib import Path

print(sys.version.split()[0])
print(sys.prefix != sys.base_prefix, "<- inside a virtual environment")
print(Path(sys.executable).name.startswith("python"))
Output
3.14.7
True <- inside a virtual environment
True

Inspecting installed packages from Python itself. importlib.metadata reads the same information pip list shows; here it confirms the pinned client library this course uses in later lessons.

# installed.py
from importlib.metadata import version, distributions

print("httpx", version("httpx"))
print("pytest", version("pytest"))
names = sorted({d.metadata["Name"].lower() for d in distributions()})
print("httpx" in names and "pytest" in names)
Output
httpx 0.28.1
pytest 9.1.1
True

A requirements file is plain text. Writing one by hand for the packages this course pins, then reading it back, shows there is nothing magic about the format.

# requirements_demo.py
from pathlib import Path

pins = {"httpx": "0.28.1", "pytest": "9.1.1", "mypy": "2.3.1", "ruff": "0.16.6"}
Path("requirements.txt").write_text("".join(f"{name}=={ver}\n" for name, ver in pins.items()), encoding="utf-8")

for line in Path("requirements.txt").read_text(encoding="utf-8").splitlines():
    name, ver = line.split("==")
    print(f"{name:<8} pinned to {ver}")
Output
httpx    pinned to 0.28.1
pytest   pinned to 9.1.1
mypy     pinned to 2.3.1
ruff     pinned to 0.16.6

The commands themselves, for reference. They are shown, not run, because creating environments is something you do in your terminal, once per project.

# setup_project.sh
python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install httpx==0.28.1
pip freeze > requirements.txt
deactivate

# the same project with uv
uv init my-project && cd my-project
uv add httpx==0.28.1
uv run python main.py

Try it yourself

In a new folder, create a virtual environment, activate it, and confirm with python -c "import sys; print(sys.prefix)" that the path points inside .venv. Install httpx==0.28.1, run pip freeze, and save it to requirements.txt. Deactivate, delete the .venv folder, recreate it, and reinstall from the requirements file to prove the round trip works. If you have uv, do the same with uv init and uv add.

Common mistakes

What's next

You can bring in the world's Python code without breaking anything. Before you reach for it, though, meet what is already built in: the standard library.

Course overview