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 tellsuv(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:
- Create the venv.
- Activate it (a shell command that puts the venv first in your
PATH). - Remember to activate it every new terminal session.
- Use
pipto 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:
- Reads
pyproject.tomlto find out which Python and packages this project needs. - Creates
.venv/if it doesn’t exist. - Installs missing packages into the venv.
- 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.