Basics & Syntax
Python’s design philosophy
Section titled “Python’s design philosophy”Python was designed around a single guiding principle: code should read like plain English. Guido van Rossum, Python’s creator, made explicit trade-offs in favour of readability — even when that meant restricting programmer freedom.
The philosophy is captured in PEP 20, known as The Zen of Python.
Run import this in any Python session to see it.
Three lines that shape everything:
- Beautiful is better than ugly.
- Explicit is better than implicit.
- There should be one — and preferably only one — obvious way to do it.
That last point matters most for learning: when you look up how to do something in Python, there is almost always a canonical answer.
Significant whitespace
Section titled “Significant whitespace”Python uses indentation (4 spaces per level, by convention) to define blocks. There are no curly braces and no semicolons.
def is_even(n: int) -> bool: if n % 2 == 0: return True return False
for i in range(5): if is_even(i): print(i, "is even")Mixing tabs and spaces causes a TabError.
The community standard (PEP 8) is 4 spaces — configure your editor once and forget about it.
The “everything is an object” thesis
Section titled “The “everything is an object” thesis”This is the single idea the rest of the course keeps coming back to: in Python, everything you can name is an object — not just the things that look like data. A number, a function, a class, a module, and even a type itself all have an identity (id()), a type (type()), and a set of attributes:
print(type(42)) # <class 'int'>print((42).bit_length()) # 6 -- an int has methods, because it's an objectprint(type(print)) # <class 'builtin_function_or_method'>print(type(int)) # <class 'type'> -- int itself is an instance of typeprint(type(type)) # <class 'type'> -- type is an instance of itselfint is not a special primitive keyword — it is an object of type type (a class), the same way Dog would be if you defined it yourself. print is not special syntax either — it is a callable object, retrievable, assignable to another name, and passable as an argument like any other value. This single fact is why almost every later lesson can be explained in terms of “what object does this name refer to, and what happens when you call a method or operator on it” — there is no separate rulebook for “primitives” versus “objects.”
How this course teaches you to read AI-generated code
Section titled “How this course teaches you to read AI-generated code”Every lesson from here on ends with two things aimed specifically at code you didn’t write yourself: a short “Reading AI-generated code” checklist, and at least one <SpotTheBug> exercise with real, broken-looking-plausible code and a hidden explanation. The bugs in those exercises are not contrived — they are the same handful of patterns (mutable default arguments, is vs ==, aliasing, forgetting is None, shadowing a builtin) that show up constantly in generated Python, because a language model reproduces common shapes of code without necessarily reasoning through Python’s specific object and scoping semantics. Treat every checklist as a list of “things to grep for” before trusting a diff, not just exam material.
What this module covers
Section titled “What this module covers”| Lesson | Core concept |
|---|---|
| Variables & Types | Names vs objects, is vs ==, mutability, truthiness |
| Control Flow | if/elif/else, match, the iterator protocol, for, while |
| Functions | def, mutable defaults, *args/**kwargs, LEGB scope |
| Strings | Methods, slicing, bytes vs str, f-strings, immutability |
Each lesson is self-contained and ends with a runnable playground, a callout, a quiz, and a progress checkpoint.
Your first Python script
Section titled “Your first Python script”The script below demonstrates the core syntax you will encounter in every lesson: variables, a function, a loop, and an f-string.
def describe(value: int) -> str: """Return a short description of a number.""" if value < 0: label = "negative" elif value == 0: label = "zero" else: label = "positive" return f"{value} is {label}"
numbers = [-3, 0, 7, 42]for n in numbers: print(describe(n))
total = sum(numbers)print(f"Sum: {total}")print("First program complete.")Loading Python runtime (first run only)…
The second playground shows the “everything is an object” thesis in action, including a checking pattern that looks reasonable but breaks once subclassing is involved.
# 1. Everything is an object -- including functions, types, and type itself.print(type(42))print((42).bit_length())print(type(print))print(type(int))print(type(type))
# 2. type(x) == SomeClass vs isinstance(x, SomeClass) with a subclass.class Animal: pass
class Dog(Animal): pass
fido = Dog()print(type(fido) == Animal) # BUG-prone check: False, even though Dog IS an Animalprint(isinstance(fido, Animal)) # True -- respects subclassingLoading Python runtime (first run only)…
Mental Model
Section titled “Mental Model”flowchart LR X["name: x"] --> ObjX["object (id, type, value)"] Describe["name: describe"] --> ObjDescribe["function object (also has id, type, value)"] Int["name: int"] --> ObjInt["class object, itself an instance of type"] Type["name: type"] --> ObjType["the class of every class, an instance of itself"]
Every box on the right side is the same kind of thing — an object with an identity and a type — no matter whether the name on the left refers to what you’d casually call “data” or “code.” Reading Python fluently means asking, for any name, “what object does this refer to right now,” rather than sorting names into separate mental categories of primitive vs. object.
Trade-offs
Section titled “Trade-offs”| What you get | Benefit | Cost |
|---|---|---|
| Uniform object model | One mental model covers numbers, functions, classes, and modules | No separate “fast path” for primitives — everything carries object overhead |
type() reveals the exact class | Precise, unambiguous introspection | Comparing type(x) == SomeClass silently rejects legitimate subclasses |
| Readability-first philosophy (PEP 20) | A canonical, discoverable way to do most things | Occasionally trades raw performance or terseness for clarity |
| 4-space indentation as syntax | Forces consistently readable block structure across any codebase | Mixing tabs and spaces is a hard error (TabError), not just a style nit |
Common Misconceptions
Section titled “Common Misconceptions”- “Numbers, strings, and booleans are primitives; only custom classes are objects.” They are all objects —
type(42)isint, a real class, and(42).bit_length()proves it has methods like any other object. - “
type(x) == SomeClassis the same asisinstance(x, SomeClass).” It is not —type(x) == SomeClassisFalsefor any subclass instance, whileisinstancerespects the whole class hierarchy. - “Python’s readability rules (PEP 8/PEP 20) are just style preferences with no real effect.” They shape what “looks right” enough that deviations are often exactly where bugs hide — a codebase that follows one obvious way to do things is easier to read for bugs, human or AI-generated.
- “A function or a class isn’t really a value the way a number is.” Both are objects that can be assigned to a variable, stored in a list, or passed as an argument —
type(print)andtype(int)prove neither is special syntax.
Reading AI-generated code
Section titled “Reading AI-generated code”- Does a type check use
type(x) == SomeClasswhereisinstance(x, SomeClass)is actually intended? The first silently rejects valid subclasses. - Does the code assume “primitives” (numbers, booleans, strings) behave differently from “real objects” — for example assuming they have no methods or can’t be subclassed?
- Whenever a lesson from here on gives you a checklist, does the code under review actually get checked against it, or does the review stop at “does it run”?
- Is a function or class being treated as something other than an ordinary value — for example, never assigned to a variable or passed as an argument, when doing so would simplify the code?
🐛 Spot the bug
def is_valid_item(item): if type(item) == dict: return "id" in item return FalseShow the answer
type(item) == dict only accepts a value whose type is exactly dict — a perfectly reasonable dict subclass (for example one returned by a library, or collections.UserDict, or a developer’s own class Config(dict): ...) fails this check even though it behaves like a dict in every way that matters here. isinstance(item, dict) checks the whole class hierarchy and accepts subclasses too. The fix: if isinstance(item, dict): return "id" in item.