Welcome to lesson one of the Python Fundamentals course. Sixty lessons, two a week, dropping every Tuesday and Friday from now until June. By the end of it you’ll have written a fair amount of Python, broken it in interesting ways, fixed it, learned the testing and packaging story, and walked through enough of the ecosystem that the words pyproject.toml, pytest, pyright, asyncio, and uv will feel like old friends rather than YouTube thumbnails.
This is not a “learn to code from zero” course. It assumes you’ve written something in some language before — maybe JavaScript at university, maybe a bit of bash at work, maybe a SQL job that grew an “If” statement and a regex and one day became a program. If you’ve never coded anything, you can still follow along, but expect to spend time on the side filling in basic concepts (variables, loops, functions) that we won’t dwell on. What we will dwell on: how to write Python the way modern Python is actually written in 2026, which is meaningfully different from how it was written even five years ago.
Before we touch any code, let’s map the territory. What is Python in 2026, what version should you actually install, and which of the changes from the last six years matter on a normal day at work?
The state of the language, May 2026
Python 3.13 is the current stable release. It came out in October 2024 and is what almost every recent tutorial, library, and Docker base image targets. Python 3.14 is in development and slated for October 2026 with the usual betas through summer. By the time this course finishes you’ll see the first 3.14 release candidates landing. We’ll target 3.13 throughout the course; nothing we do will break on 3.14.
Python 2 died in 2020. If anyone tells you they “still write a bit of Python 2,” nod sympathetically and move on. It is gone. The migration is over. Six years of post-funeral grief is enough.
Python 3.0 through 3.7 are also deprecated and unsupported. If you find yourself on a system shipping 3.6 or 3.7, you are doing maintenance archaeology, and you should start by figuring out how to install a modern interpreter alongside it without breaking the system Python. (We’ll cover exactly this in lesson 4.)
Python 3.8 through 3.12 are still very much around. 3.10 and 3.11 in particular are everywhere — Ubuntu 22.04 LTS shipped 3.10, Ubuntu 24.04 LTS shipped 3.12, and a lot of company laptops are still on whatever the OS gave them. You should know that the syntax we’ll use targets 3.10 as the realistic minimum. Anything older and you’ll have to rewrite a few examples.
So: install 3.13, write code that runs on 3.10+, don’t lose sleep over older versions unless your job requires it.
What changed and is worth knowing
Six years of Python releases, distilled into the things that actually changed how working Python is written.
Pattern matching (3.10). The match / case statement. It looks like a switch from C-family languages but is meaningfully more powerful: it destructures, it matches on types and shapes, and it integrates with dataclasses and named tuples. Some people overuse it; some people pretend it doesn’t exist. Both are wrong. We’ll cover it properly in lesson 3.
The | union operator for types (3.10). You used to write Optional[int] or Union[int, str]. Now it’s int | None and int | str. Cleaner, no import. Type checkers love it. Use it. Lesson 2 dives in.
Better error messages (3.11). The traceback now points to the exact subexpression that exploded, not just the line. If you’ve ever stared at a KeyError on a line with five dictionary lookups and wondered which one was the culprit, this single change saves you hours per month. It’s quiet, it’s free, it just works.
Faster CPython (3.11+). The “Faster CPython” project led by Guido and Mark Shannon shipped a chunk of interpreter speedups in 3.11, more in 3.12, and an opt-in JIT compiler in 3.13. Real-world workloads typically see 10-25% speedups going from 3.10 to 3.13 with no code changes. The JIT in 3.13 is opt-in and experimental — you build it with --enable-experimental-jit and benchmark before trusting it in production. By 3.15 it’ll likely be the default and we’ll all forget the transition happened.
New typing syntax (3.12). PEP 695 introduced a cleaner way to declare generic types and type aliases:
# Old (still works)
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
# New, 3.12+
def first[T](items: list[T]) -> T:
return items[0]
type Json = dict[str, "Json"] | list["Json"] | str | int | float | bool | None
Most of the ecosystem has now moved to this. We’ll use it from lesson 2 onward.
Free-threaded build (3.13, experimental). This is the big one in the abstract: a build of Python without the Global Interpreter Lock. The GIL is the lock that’s prevented true parallelism in pure-Python code since 1992. PEP 703 was approved, and 3.13 ships an optional free-threaded build (python3.13t). Right now in May 2026 it is still experimental, libraries are still being audited for thread-safety, and you should not run production workloads on it unless you’ve measured very carefully. But it works, it’s real, and it’ll move from experimental to stable over the next two or three releases. The era of “Python can’t do CPU-bound parallelism” is ending. We’ll touch it briefly in module 8 (concurrency).
asyncio reaching maturity (everything since 3.7). async/await has stopped being controversial. Every major web framework, every database driver worth using, every cloud SDK now has a working async story. Module 8 of this course is dedicated to it.
The ecosystem shift you actually need to know about
Language features are easy to summarise. The bigger change in the last two years is the tooling shift, and it’s the one that genuinely affects how you’ll spend your day.
Packaging is being eaten by uv. For about twenty years the answer to “how do I install a Python package” was pip install. The answer to “how do I isolate it from system Python” was python -m venv. The answer to “how do I lock my dependencies” was pip-tools or poetry or pipenv depending on which year you graduated. It was a mess.
In 2024 a company called Astral (the same people behind ruff) released uv, written in Rust, doing all of the above (and replacing pyenv for installing interpreters) in a single binary, roughly 10-100x faster than pip. By the end of 2025 it had become the default in serious Python shops for new projects. By May 2026 a freshly-set-up Python developer machine is, increasingly, just uv and an editor.
We’ll install uv in lesson 4 and use it for the rest of the course. If you’ve used pip and venv before, the muscle memory transfers; you just type uv instead of python -m venv and pip install, and everything is faster. If you’ve never used either, even better — you’ll learn the modern way first and not have to unlearn the old one.
pip, virtualenv, and poetry are not going anywhere. Existing projects still use them. You will absolutely encounter requirements.txt and pyproject.toml and Pipfile.lock files in the wild for years to come. We’ll cover what each is and how to deal with them. But for new projects, in 2026, uv is the default.
Linting and formatting consolidated into ruff. Same company. Same Rust speed. ruff replaced flake8, pylint (sort of), isort, and black with a single tool that runs on every save and is too fast to notice. If your editor isn’t running ruff on save by lesson 8 of this course, something is wrong.
Type checking: pyright ate mypy’s lunch. Both still exist, both still work. mypy (the original, from Dropbox/Guido) is more configurable and has slightly different opinions on edge cases. pyright (from Microsoft, written in TypeScript, included in VS Code via the Pylance extension) is meaningfully faster and is what most new code is checked against. If you use VS Code or Cursor, you’re using pyright whether you noticed or not. We’ll cover both, but the default in this course is pyright.
AI assistants are part of the toolchain now. Copilot, Claude, Cursor — pretending they don’t exist is not a serious position in 2026. We’ll talk in lesson 2 about how to write code in a way that makes them genuinely useful (hint: type hints), and we’ll talk later in the course about when their suggestions are likely to be wrong (mostly: anything involving subtle async behaviour, anything involving the data layer of your specific company, and anything where they confidently invent a library that doesn’t exist).
”Python is slow” — addressing it once, then moving on
You will hear, forever, that Python is slow. It’s not wrong, exactly, but it’s the wrong frame.
Python is slow at CPU-bound computation in a tight loop in pure Python. A for loop doing arithmetic in pure Python is roughly 50x slower than the equivalent C. This is a real fact and it has not fundamentally changed.
But almost no real Python job is that. Real Python jobs do this kind of work:
- Receive an HTTP request, query a database, format JSON, return a response. The bottleneck is the database round-trip and the network. Python overhead is in the noise.
- Read a Parquet file with
polarsorpyarrow, transform some columns, write it out. The actual computation runs in Rust or C++ called from a thin Python orchestration layer. Python overhead is in the noise. - Train a machine learning model with
pytorch. The math runs on a GPU in CUDA. Python is the conductor; it’s not in the orchestra. - Glue three SaaS APIs together. The bottleneck is HTTP latency. Python overhead is in the noise.
Where Python is genuinely the wrong answer is: real-time game engines, hot-path numerical code without numpy/torch/jax, embedded systems with kilobytes of RAM, and the kind of high-frequency trading where a microsecond costs you a million dollars. For everything else — the work most of us do — Python is fast enough, getting faster every release, and the productivity gain pays for the runtime cost a hundred times over.
This is the last time we’ll discuss it. If someone in a code review tells you “Python is slow, you should rewrite this in Go,” ask them to benchmark first. They almost never do.
What’s coming in the rest of the course
Ten modules, roughly:
- Python in 2026 (you are here) — language overview, environment setup, the modern toolchain.
- Syntax and types — variables, control flow, functions, type hints, the new generics.
- Data structures — lists, dicts, sets, tuples, dataclasses, comprehensions, when to reach for each.
- Iterators, generators, and the iteration protocol — the engine room of Python; understanding this changes everything.
- Object-oriented Python — classes, inheritance, dunder methods, protocols, the
dataclassstory. - Errors, logging, and debugging — exceptions,
logging,pdb, profiling, what good looks like. - The standard library tour —
pathlib,datetime,collections,itertools,functools,subprocess,json, theosmodule. - Concurrency — threads, processes,
asyncio, free-threaded Python, when to use which. - Testing and packaging —
pytest, fixtures, mocking,pyproject.toml, publishing to PyPI, semantic versioning. - A real project, end to end — we build a small but realistic CLI app, with tests, types, a CI pipeline, and a release. The final ten lessons.
Lesson 2 starts with the unsexy-but-critical topic that every modern Python codebase now expects: type hints. They are not optional anymore, even though the runtime still pretends they are.
Further reading
- The official Python documentation — your home base.
- What’s New in Python 3.13 — the release notes are surprisingly readable.
- PEP 703 — Making the GIL Optional in CPython — the free-threading proposal.
- The Astral blog — keeping up with
uvandruff.
See you Friday for type hints.