Python
The data model, iteration protocol, typing, and async machinery under the Python you write every day. These are the parts that get fuzzy first.
73 concepts434 live tasks
Start practicing freeWhat's inside
Objects, Names & Mutability
Assignment never copies: names bind to objects
`is` vs `==`, and why `256 is 256` but `257 is 257` may not be
Default arguments are evaluated once, at def time
A shallow copy shares every element with the original
Immutable containers can hold mutable objects
`+=` mutates in place or rebinds, depending on the type
What actually makes an object usable as a dict key
Refcounting frees most objects; the cycle collector gets the rest
Functions, Scope & Closures
LEGB: how Python actually finds a name
Closures capture variables, not values
Every lambda in your loop sees the final value of `i`
Assignment makes it local; `nonlocal` and `global` opt out
`*args`, `**kwargs`, and unpacking on both sides of the call
`/` and `*` in signatures: controlling how arguments are passed
Functions are objects: pass them, store them, partially apply them
Comprehensions & the Iteration Protocol
What a `for` loop actually does
Iterators are one-shot; the second loop gets nothing
Calling a generator function runs zero lines of its body
Generator expressions vs list comprehensions: laziness is the contract
Comprehensions have their own scope (mostly)
`yield from` is delegation, not a loop
Generators are coroutines: `send`, `throw`, `close`
A `StopIteration` raised inside a generator becomes `RuntimeError`
The Data Model (Dunders)
Dunder dispatch looks at the type, not the instance
`__repr__` is for developers, `__str__` is for output
Define `__eq__` and your class silently becomes unhashable
How `x + y` really resolves: `__add__`, `__radd__`, `NotImplemented`
`__getitem__` receives slice objects, and `in` has a fallback chain
Truthiness is a protocol: `__bool__`, then `__len__`
`__call__` makes instances callable: stateful functions
`__new__` creates, `__init__` configures
Classes, Dataclasses & Attribute Lookup
Class attributes are shared until an instance shadows them
`__getattr__` is the fallback; `__getattribute__` is the firehose
The MRO: C3 linearization, not depth-first search
`super()` calls the next class in the MRO, not the parent
dataclass vs NamedTuple vs TypedDict: three different runtime shapes
Dataclass mutable defaults need `field(default_factory=...)`
`__slots__` trades the instance dict for fixed attribute slots
Metaclasses, and the two hooks that usually replace them
Decorators, Context Managers & Descriptors
A decorator is just `f = deco(f)`, executed at def time
`functools.wraps`, or your decorator erases the function's identity
`@retry(times=3)` needs three layers, not two
`with` is `try/finally` speaking a protocol, and `__exit__` can eat exceptions
`@contextmanager` generators and `ExitStack` for dynamic cleanup
Descriptors: the lookup order that explains properties, methods, and slots
`property` vs `functools.cached_property`: one caches, and that's why
Bound methods are manufactured on every attribute access
Typing & Static Analysis
Type hints don't run: annotations are metadata, not checks
`list[int]` and `int | None` (the `typing` imports you no longer need)
Type narrowing: how the checker learns what you already know
`Protocol`: duck typing the checker can verify
Generics: making the checker track types through your code
Why `list[int]` is not a `list[object]`: variance
`Any` turns the checker off; `object` turns it up
Forward references, `TYPE_CHECKING`, and import-cycle escape hatches
Async, Threads & the GIL
The GIL: threads for waiting, processes for working
One thread, many tasks: the event loop only switches at `await`
Calling a coroutine function runs nothing
One blocking call freezes every task on the loop
`create_task` starts work; bare `await` waits for it, and tasks can be GC'd
Cancellation is an exception injected at the next await
What `await` compiles to: generators all the way down
`gather` vs `TaskGroup`: what happens when one task fails
The GIL doesn't make your code thread-safe
Stdlib Idioms & Performance
EAFP: try it and catch the failure, don't check first
`Counter`, `defaultdict`, `deque`: stop hand-rolling these
itertools: lazy iteration building blocks worth re-memorizing
`lru_cache` is easy (until it pins objects and mixes up arg forms)
f-strings do more than interpolate: `=`, `!r`, and format specs
match/case: a bare name never compares; it captures
Sort with `key=`, and lean on stability for multi-level ordering
O(1) membership needs a set; string building needs `join`
`raise ... from ...`, exception groups, and `except*`

