Imagine you’re working on two projects:

  • Project A uses NumPy 1.26
  • Project B uses NumPy 2.1

If you install NumPy globally on your machine, only one version exists at a time. Switching projects means uninstalling and reinstalling. Worse — your operating system might also depend on a specific Python and break if you upgrade it.

A virtual environment solves this. It’s a self-contained Python installation, just for one project. Every project gets its own. Installing or removing libraries in one virtual environment can’t affect any other.

Create a project with uv

Go to your project folder:

cd ~/code/python-fundamentals

Initialise a new uv project:

uv init

You’ll see a few new files appear:

python-fundamentals/
├── .python-version
├── README.md
├── main.py
└── pyproject.toml

What each one is for:

  • pyproject.toml — the modern Python project file. It tells uv (and the rest of the Python ecosystem) the project’s name, version, dependencies, and Python version.
  • .python-version — pins the Python version this project uses (e.g. 3.13).
  • main.py — a starter Python file. Feel free to replace it with your own.
  • README.md — a description of the project.

Open pyproject.toml:

[project]
name = "python-fundamentals"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []

This file is the single source of truth for your project’s setup. We’ll fill in dependencies in the next lesson.

Create the virtual environment

The first time you run anything with uv inside this folder, it creates a virtual environment automatically. Try it:

uv run python main.py
Hello from python-fundamentals!

Look at your folder again — there’s now a .venv/ directory:

python-fundamentals/
├── .python-version
├── .venv/           ← your virtual environment lives here
├── README.md
├── main.py
└── pyproject.toml

.venv/ contains a private copy of Python and any packages this project uses. You never have to look inside it. Add it to .gitignore if you use git — it’s recreatable from pyproject.toml.

uv init actually creates a .gitignore for you with .venv/ already in it. Open it to check.

Point VS Code at the virtual environment

Press Cmd+Shift+P (or Ctrl+Shift+P) and run Python: Select Interpreter. Pick the one that includes .venv in its path:

('.venv': uv) ./.venv/bin/python

Now VS Code’s autocompletion, type checking, and run button all use this project’s environment.

The old way

For context, here’s what creating a virtual environment looks like without uv:

python3 -m venv .venv
source .venv/bin/activate    # on macOS/Linux
# .venv\Scripts\activate     # on Windows
pip install numpy

You had to:

  1. Create the venv.
  2. Activate it (a shell command that puts the venv first in your PATH).
  3. Remember to activate it every new terminal session.
  4. Use pip to install packages — no lockfile, so versions could drift.

uv skips activation entirely. Every uv run command uses the project’s venv automatically. The only price is learning a different command name.

What uv run actually does

When you type:

uv run python main.py

uv does roughly this:

  1. Reads pyproject.toml to find out which Python and packages this project needs.
  2. Creates .venv/ if it doesn’t exist.
  3. Installs missing packages into the venv.
  4. Runs the command (python main.py) inside the venv.

All of that happens in a fraction of a second. You don’t have to think about it.

What’s next

You have a clean, isolated Python environment for the course. Next we’ll install our first third-party library and learn how dependencies work.

Toggle theme (T)