Day 3 — Virtual Environments#
Goal: Understand why virtual environments exist, how they work, and how to create and use them — so your projects never break each other’s dependencies.
The Problem: Why Virtual Environments?#
Imagine you have two Python projects:
Project A — needs pandas==1.5.0
Project B — needs pandas==2.1.0If you install both globally (system-wide), only one version of pandas can exist at a time. Installing one breaks the other.
# Without virtual environments:
pip install pandas==1.5.0 # Project A works
pip install pandas==2.1.0 # Project A BREAKS, Project B worksThis is called dependency hell.
🧠 Knowledge Check#
Q1: What problem do virtual environments solve?
- A) They make Python code run faster
- B) They allow you to write Python code in a web browser
- C) They prevent dependency conflicts by allowing different projects to have their own isolated package versions
- D) They hide your code from other users
Answer
C — Virtual environments isolate dependencies so that upgrading a package in Project A doesn’t accidentally break Project B.
The Solution: Virtual Environments#
A virtual environment is an isolated Python installation for a single project. Each project gets its own set of packages.
System Python (/usr/bin/python3)
│
├── Project A (/home/you/project-a/.venv/)
│ └── pandas==1.5.0
│ └── numpy==1.24.0
│
└── Project B (/home/you/project-b/.venv/)
└── pandas==2.1.0
└── numpy==1.26.0Each .venv/ folder contains:
- A copy/link to the Python interpreter
- Its own
pip - Its own installed packages
Rule: Every Python project should have its own virtual environment. Never install project dependencies globally.
How Virtual Environments Work#
When you activate a virtual environment, your shell temporarily changes which python and pip commands point to:
# Before activation:
which python3
# /usr/bin/python3 ← system Python
# After activation:
source .venv/bin/activate
which python3
# /home/you/project/.venv/bin/python3 ← project's PythonThe activation modifies your PATH so .venv/bin/ comes first.
Creating Virtual Environments — Three Methods#
Method 1: Using python3 -m venv (built-in)#
cd ~/my-project
# Create the virtual environment:
python3 -m venv .venv
# Activate it:
source .venv/bin/activate
# Your prompt changes:
# (.venv) alice@machine:~/my-project$
# Install packages (isolated):
pip install requests
# Deactivate when done:
deactivateMethod 2: Using uv (recommended — much faster)#
cd ~/my-project
# Create the virtual environment:
uv venv
# Activate it:
source .venv/bin/activate
# Install packages (blazing fast):
uv pip install requests
# Deactivate:
deactivateMethod 3: Let uv manage it automatically#
With uv, you often don’t need to manually create or activate environments:
# uv init creates a project with its own environment:
uv init my-project
cd my-project
# uv add installs packages into the project's environment:
uv add requests
# uv run executes commands inside the environment:
uv run python main.py
# No manual activation needed!We recommend Method 3 for this course.
uvhandles environments automatically.
The .venv Directory#
ls -la .venv/
# bin/ ← python, pip, activate (the executables)
# lib/ ← installed packages
# include/ ← C header files (for compiled packages)
# pyvenv.cfg ← configuration fileImportant: .venv should NOT be committed to Git#
Add it to .gitignore:
echo ".venv/" >> .gitignoreWhy? The .venv/ directory is large (can be hundreds of MB) and is machine-specific. Instead, you share a requirements.txt or pyproject.toml so others can recreate it.
Dependency Files#
requirements.txt (traditional)#
# Generate from current environment:
pip freeze > requirements.txt
# Install from file:
pip install -r requirements.txtExample requirements.txt:
requests==2.31.0
pandas==2.1.4
numpy==1.26.2pyproject.toml (modern — used by uv)#
[project]
name = "my-project"
version = "0.1.0"
dependencies = [
"requests>=2.31",
"pandas>=2.1",
]uv.lock (lockfile — exact versions)#
uv generates a lockfile that pins exact versions for reproducibility:
uv lock # creates/updates uv.lock
uv sync # installs exactly what's in uv.lockCommit
pyproject.tomlanduv.lockto Git. Do NOT commit.venv/.
🧠 Knowledge Check#
Q1: What is the purpose of the uv.lock file?
- A) It encrypts your source code
- B) It prevents other users from editing your project
- C) It records the exact versions of all installed dependencies to ensure everyone working on the project has an identical environment
- D) It locks the virtual environment so it cannot be deleted
Answer
C — A lockfile guarantees reproducibility by pinning exact package versions (e.g., requests==2.31.0 rather than just requests>=2.31).
Common Workflow#
# 1. Create project
uv init my-tool
cd my-tool
# 2. Add dependencies
uv add httpx rich
# 3. Write code
cat > main.py << 'EOF'
import httpx
from rich import print
response = httpx.get("https://httpbin.org/get")
print(response.json())
EOF
# 4. Run code
uv run python main.py
# 5. Share with teammate
# They clone your repo and run:
uv sync # installs exact same packages
uv run python main.pyQ&A#
Q: What happens if I forget to activate the virtual environment?
A: You’ll use the system Python and system packages instead of your project’s. This means:
- Packages you installed in the venv won’t be available
- Packages you install will go to the system Python (affecting all projects)
With uv run, you don’t need to activate — it automatically uses the project’s environment.
Q: Can I delete .venv/ and recreate it?
A: Yes! The .venv/ is completely regenerable from your pyproject.toml and uv.lock:
rm -rf .venv
uv sync # recreates .venv with exact same packagesThis is why you should never commit .venv/ to Git — it’s a derived artifact.
Q: What is the difference between pip install and uv add?
A:
pip install requests— installs the package but doesn’t record it anywhere permanentlyuv add requests— installs the package AND adds it topyproject.tomlAND updatesuv.lock
With uv add, your dependencies are always tracked in your project configuration.
Q: Why not just install everything globally?
A: Because:
- Version conflicts — different projects need different versions
- Reproducibility — you can’t tell which packages belong to which project
- Cleanup — uninstalling a project’s packages is a nightmare
- Collaboration — teammates can’t reproduce your exact environment
Q: How do I know if my virtual environment is active?
A: Look at your terminal prompt. When active, you’ll see (.venv) at the beginning:
(.venv) alice@machine:~/project$You can also check:
which python3
# If it shows .venv/bin/python3 → venv is active
# If it shows /usr/bin/python3 → venv is NOT activeExercises#
Exercise 1: Create a virtual environment manually
mkdir -p /tmp/venv-test
cd /tmp/venv-test
# Create a venv:
python3 -m venv .venv
# Check what was created:
ls .venv/bin/What do you see in .venv/bin/?
activate activate.csh activate.fish
pip pip3 pip3.12
python python3 python3.12These are the virtual environment’s executables. When you activate, python3 points to .venv/bin/python3 instead of /usr/bin/python3.
Exercise 2: Activate and install
cd /tmp/venv-test
# Activate:
source .venv/bin/activate
# Check:
which python3
pip install rich
# Test:
python3 -c "from rich import print; print('[bold green]It works![/bold green]')"
# Deactivate:
deactivate
# Try again (should fail):
python3 -c "from rich import print; print('test')"What happens after deactivate?
After deactivate, python3 points back to the system Python, which doesn’t have rich installed. You’ll get:
ModuleNotFoundError: No module named 'rich'This proves the virtual environment is isolated.
Exercise 3: Use uv to manage a project
cd /tmp
uv init my-demo
cd my-demo
# Add a dependency:
uv add httpx
# Check what was created:
cat pyproject.toml
ls -la
# Write and run a script:
echo 'import httpx; print(httpx.get("https://httpbin.org/ip").json())' > main.py
uv run python main.pyWhat does this do?
uv initcreates a project withpyproject.toml,README.md, etc.uv add httpxinstallshttpxinto the project’s.venv/and records it inpyproject.tomluv run python main.pyruns the script using the project’s Python environment
The output should show your public IP address (from the httpbin API).
Exercise 4: MCQ
Q1: What does a virtual environment isolate?
- A) Your files from other users
- B) Your Python packages from other projects
- C) Your terminal from the rest of the system
- D) Your code from the internet
Answer
B — A virtual environment gives each project its own set of Python packages, preventing version conflicts between projects.
Q2: Which file should be committed to Git?
- A)
.venv/ - B)
pyproject.toml - C)
.venv/lib/ - D)
.venv/bin/python3
Answer
B — pyproject.toml (and uv.lock) should be committed. The .venv/ directory should be in .gitignore — it’s large and machine-specific.
Q3: What does uv run python script.py do differently from python3 script.py?
- A) Nothing — they are identical
- B)
uv runautomatically uses the project’s virtual environment - C)
uv runruns Python faster - D)
uv runrequires the internet
Answer
B — uv run ensures the command runs inside the project’s virtual environment, even if you haven’t manually activated it. It also ensures dependencies are synced first.
Clean up:
rm -rf /tmp/venv-test /tmp/my-demo