03 — Development Environment¶
The development environment is where you spend 90% of your working hours. A bad setup creates friction on every action: slow autocomplete, broken debugger, inconsistent formatting, notebooks that pollute git history with 50MB of embedded outputs. This document gives you the setup that eliminates that friction from Day 1.
The core choice in 2025: VS Code with the Jupyter extension beats both standalone JupyterLab and the classic Jupyter Notebook interface for most ML workflows. You get the notebook experience inside a full IDE — with a real debugger, Git integration, terminal, and extension ecosystem.
VS Code Setup for ML¶
Install VS Code¶
Download from code.visualstudio.com. On macOS:
# Via Homebrew
brew install --cask visual-studio-code
# Or download the .dmg directly from the site
Essential Extensions¶
Install these extensions. In VS Code: Cmd+Shift+X → search by name → Install.
Extension |
Publisher |
Why It’s Essential |
|---|---|---|
Python |
Microsoft |
Base Python support: syntax highlighting, import resolution, environment selection |
Pylance |
Microsoft |
Fast type checking, autocomplete, IntelliSense. Required. |
Jupyter |
Microsoft |
Run .ipynb notebooks directly in VS Code. Variable explorer, plot viewer. |
GitLens |
GitKraken |
Inline git blame, branch history, diff viewer. Makes git usable in-editor. |
Black Formatter |
Microsoft |
Auto-format Python on save (uses Black). |
Ruff |
Charliermarsh |
Fast Python linter (replaces flake8, isort, many pylint rules). Rust-based, instant. |
indent-rainbow |
oderwat |
Colored indentation levels. More useful than it sounds for reading nested code. |
Path Intellisense |
Christian Kohler |
Autocomplete file paths in strings. Essential for ML data paths. |
GitHub Copilot |
GitHub |
AI autocomplete. If you have access, useful. Not a substitute for understanding. |
Optional but useful:
Rainbow CSV — Syntax highlighting for CSV files
Docker — If you’re containerizing ML workflows
Remote - SSH — Connect to cloud VMs directly from VS Code (critical for Vast.ai/RunPod workflows)
Markdown All in One — For writing README/documentation
VS Code Settings for ML¶
Open settings: Cmd+, → switch to JSON view (top-right icon). Add:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-python.black-formatter",
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
"python.analysis.typeCheckingMode": "basic",
"jupyter.notebookFileRoot": "${workspaceFolder}",
"jupyter.sendSelectionToInteractiveWindow": true,
"files.autoSave": "afterDelay",
"files.autoSaveDelay": 1000,
"editor.rulers": [88],
"python.analysis.extraPaths": ["${workspaceFolder}/src"],
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.codeActionsOnSave": {
"source.organizeImports": "always"
}
}
}
What this does:
Formats Python on every save with Black (88-char line length)
Sets the project
.venvas the default interpreterShows a ruler at 88 characters (Black’s default)
Auto-saves files after 1 second
Jupyter vs JupyterLab vs VS Code Notebooks¶
An honest comparison:
Feature |
Classic Jupyter |
JupyterLab |
VS Code + Jupyter |
|---|---|---|---|
Debugger |
❌ Limited |
⚠️ Basic |
✅ Full Python debugger |
Variable explorer |
❌ |
⚠️ |
✅ Built-in |
Git integration |
❌ |
⚠️ Extension |
✅ Native GitLens |
Autocomplete |
⚠️ Basic |
⚠️ Basic |
✅ Pylance (excellent) |
Terminal |
⚠️ Separate tab |
✅ Integrated |
✅ Integrated |
Extensions |
Limited |
JupyterLab extensions |
VS Code extensions (massive ecosystem) |
|
❌ |
⚠️ |
✅ Full IDE |
Startup time |
Fast |
Medium |
Medium |
Remote access |
✅ Browser-based |
✅ Browser-based |
Via Remote SSH |
Verdict: Use VS Code for local development. Use JupyterLab when you need browser-based access to a remote server (Kaggle/Colab have their own interfaces anyway). Never use the classic Jupyter Notebook (jupyter notebook) for new work — it’s the 2012 interface.
Notebook Hygiene: nbstripout¶
Jupyter notebooks store outputs (plots, print statements, arrays) as JSON inside the .ipynb file. A single notebook with matplotlib plots can balloon to 5MB. Multiply this across a project and you have a multi-hundred-MB git history full of useless binary data.
Solution: nbstripout. It strips outputs from notebooks before every git commit.
# Install
pip install nbstripout
# Enable in your git repo (one-time setup per repo)
nbstripout --install
# Verify it's installed
cat .git/config | grep nbstripout
# Should show: filter = nbstripout
# Optional: add to .gitattributes for the whole team
nbstripout --install --attributes .gitattributes
After this, git add on any .ipynb file will automatically strip outputs. Your notebook files stay small. Your git history stays clean.
What most people get wrong: They commit notebooks with outputs and then wonder why their git repo is 500MB. By the time you notice, the history is already bloated. Install nbstripout on Day 1.
Code Quality: Black + Ruff + isort¶
These three tools handle formatting, linting, and import sorting. In 2025, Ruff replaces both flake8 and isort (and many pylint rules) with a single, much faster tool. Black handles formatting.
Install¶
pip install black ruff pre-commit
# or with uv:
uv add --dev black ruff pre-commit
Configure in pyproject.toml¶
[tool.black]
line-length = 88
target-version = ['py311']
include = '\.pyi?$'
[tool.ruff]
line-length = 88
target-version = "py311"
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
]
ignore = [
"E501", # line too long (Black handles this)
]
[tool.ruff.isort]
known-first-party = ["src"]
Pre-commit hooks (run formatters automatically before every commit)¶
# Create .pre-commit-config.yaml
cat > .pre-commit-config.yaml << 'EOF'
repos:
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.7
hooks:
- id: ruff
args: [--fix]
- repo: https://github.com/kynan/nbstripout
rev: 0.7.1
hooks:
- id: nbstripout
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
args: ['--maxkb=1000']
EOF
# Install the hooks
pre-commit install
# Test: run on all files
pre-commit run --all-files
After this, every git commit will automatically:
Format your Python files with Black
Lint and fix imports with Ruff
Strip notebook outputs with nbstripout
Reject files larger than 1MB
The Productive ML Development Workflow¶
The correct ML workflow in 2025:
Explore in notebook → Refactor to .py → Track with W&B → Version with Git
Phase 1: Exploration (notebook)
Use notebooks for:
Data exploration and visualization
Trying out model architectures
Debugging outputs at each step
Quick experiments that you might throw away
Keep notebooks in a notebooks/ directory. Name them with numbers and descriptions: 01_data_exploration.ipynb, 02_baseline_model.ipynb.
Phase 2: Production (.py files)
Once you know something works, refactor it into Python modules:
src/
data/
dataset.py # Dataset class, data loading
transforms.py # Data augmentations
models/
transformer.py # Model architecture
layers.py # Custom layers
training/
trainer.py # Training loop
losses.py # Loss functions
utils/
metrics.py # Evaluation metrics
logging.py # Logging utilities
train.py # Entry point
evaluate.py # Evaluation script
Why this matters: A notebook is not a reproducible artifact. A Python script with CLI arguments is. The moment you want to run the same experiment 5 times with different hyperparameters, you need .py files, not notebooks.
Phase 3: Track everything
From the first real training run, use W&B (free tier):
import wandb
wandb.init(
project="my-ml-project",
config={
"learning_rate": 1e-4,
"batch_size": 32,
"epochs": 10,
"architecture": "resnet50",
}
)
for epoch in range(epochs):
train_loss = train_one_epoch(model, train_loader, optimizer)
val_loss, val_acc = evaluate(model, val_loader)
wandb.log({
"epoch": epoch,
"train_loss": train_loss,
"val_loss": val_loss,
"val_accuracy": val_acc,
})
wandb.finish()
Keyboard Shortcuts Worth Learning (VS Code)¶
Shortcut (macOS) |
Action |
|---|---|
|
Command palette — search any command |
|
Quick file open |
|
Toggle comment |
|
Select next occurrence (multi-cursor) |
|
Find in all files |
|
Go to definition |
|
Find all references |
`Ctrl+Shift+`` |
Open new terminal |
|
Toggle sidebar |
In notebook: |
Run cell, move to next |
In notebook: |
Run cell, stay |
In notebook: |
Insert cell above / below (command mode) |
Return to README.md · Previous: 02_gpu_and_compute_strategy.md · Next: 04_version_control_for_ml.md