All Sessions GitHub

Glossary

Short definitions for the words this course uses again and again. Search, then jump back into the session that shows the idea in memory.

No terms match that search.

Objects and names

Name

A label in a namespace, such as x. It does not store a value. It points at an object. See Session 01.

Object

Everything in Python is an object: numbers, strings, lists, functions, classes, instances. An object has identity, a type, and a value. Objects live on the heap.

Reference

The connection from a name (or a container slot) to an object. Assignment creates or changes a reference. It does not copy the object unless you ask for a copy.

Identity

Which object something is, not what it looks like. id(x) and x is y test identity. Two names with the same identity are aliases.

Equality

== asks whether two objects have the same value. They can be equal and still be different objects.

Aliasing

Two or more names (or slots) pointing at the same object. Mutations through any alias are visible to all of them.

Rebinding

x = something_else makes the name point at a different object. The old object is unchanged; it just loses one reference.

Mutation

Changing an object in place without creating a new one. lst.append(1) mutates. The object's id() stays the same.

Mutable / immutable

A mutable object can change its contents (list, dict, set, instances). An immutable object cannot (int, str, tuple). "Changing" an immutable object always rebinds the name instead.

Reference count

How many names or containers currently point at an object. In CPython, when this hits zero the object is freed immediately. Cycles need a separate collector.

Heap

The region of memory where objects live. Every Python object (an int, a list, a function, a class) is allocated on the heap. Names live in namespaces and point into it.

Call stack

The pile of frames for calls currently in progress. Each call pushes a frame; each return pops one. A traceback is a printout of this stack. See Session 02.

Garbage collection

Freeing objects nothing refers to any more. CPython does this mainly by reference counting: the instant a count hits zero the object is freed. A separate cyclic collector handles objects that only refer to each other.

Small-integer cache

CPython builds the integers -5 to 256 once at start-up and hands out the same object every time, so a = 42; b = 42 gives two names for one object. Those objects are never freed. It's an implementation detail, not a language guarantee, so never write code that depends on it.

Interning

CPython reuses some immutable objects, especially small integers (typically -5 to 256) and some strings. That is an optimisation, not a language rule. Do not write code that depends on it.

Functions and scope

Function object

def creates a function object and binds a name to it. The body does not run at definition time. See Session 02.

Frame

The temporary workspace for one function call: local names, the current line, and the path back to the caller. Frames stack; the top one is running.

LEGB

Name lookup order: Local, Enclosing function scopes, Global, Built-ins. Assignment creates a local name unless you declare global or nonlocal.

Argument binding

A parameter is a local name bound to the object the caller passed. Python does not copy that object. Mutating it is visible to the caller; rebinding the parameter is not.

Closure

An inner function plus the outer names it still needs after the outer call returns. nonlocal updates a remembered outer name instead of creating a new local.

Default argument

Default values are created once, when the function is defined, and stored on the function object. A mutable default is shared across calls, which is a classic surprise.

Containers

Container

An object whose contents are references to other objects. Lists, dicts, tuples, and sets are containers. See Session 03.

Shallow copy

A new outer container filled with the same inner references. list.copy() and dict.copy() are shallow. Nested objects stay shared.

Deep copy

A recursive copy of nested objects. Use it only when you need nested independence. Some objects should stay shared on purpose.

Hashable

An object with a stable hash, required for dict keys and set elements. Built-in immutables are usually hashable. Lists and dicts are not.

Classes

Class object

class creates a class object. It holds methods and class attributes. Calling the class creates an instance. See Session 04.

Instance

An object created by calling a class. It has its own identity and usually its own __dict__ for instance attributes.

self

The conventional name for the instance a method is operating on. Python passes it automatically when you write obj.method().

__dict__

The mapping that stores an object's writable attributes. Instance attributes live on the instance dict. Methods usually live on the class dict.

Attribute lookup

Python looks on the instance first, then the class, then base classes (the MRO). That is why a class attribute is visible on every instance until one instance shadows it.

Bound method

Accessing a function on an instance creates a method object that already remembers self. g.hello is not the raw function; it is hello bound to g.

__init__

The initialiser. After the instance exists, Python calls __init__(self, ...) to write attributes. It returns None. The caller receives the instance, not that return value. See Session 04.

__new__

The allocator. Calling a class first creates the instance, then initialises it. You almost never override __new__. The important picture: the object exists before __init__ runs.

Class attribute

A name stored on the class object. Every instance can read it until it assigns the same name on itself. A mutable class attribute (a list on the class) is one shared object.

Instance attribute

A name stored in the instance __dict__. Assignment on an instance usually creates one of these. It shadows a class attribute of the same name for that instance only.

MRO

Method resolution order: the list of classes Python searches for an attribute. For class Dog(Animal) it is Dog → Animal → object. Subclasses do not copy methods; they walk this list.

Iteration

Iterable

An object you can loop over. It can produce an iterator via iter(obj). Lists, strings, dicts, and generators are iterable. See Session 05.

Iterator

An object with state that yields one item at a time through next(). When it is exhausted it raises StopIteration. A for loop hides this protocol.

Generator

A function that uses yield. Calling it does not run the body; it returns a generator object. Each next() resumes a suspended frame until the next yield.

Lazy evaluation

Work is done only when the next value is requested. A generator expression keeps one pending computation; a list comprehension builds every value immediately.

StopIteration

The signal that an iterator is exhausted. A for loop catches it and exits. The collection is unchanged; the iterator is spent. A new for on a list creates a new iterator. A generator does not rewind.

Decorators

Decorator

A callable that takes a function and returns a replacement. @deco above a def is shorthand for f = deco(f): your function is built, handed to deco, and the name is bound to whatever comes back. See Session 06.

Wrapper

The function a decorator returns. It usually calls the original and adds something around it. After decorating, your name points at the wrapper, not at the function you wrote.

Closure cell

The small box that keeps an outer name alive for an inner function. It is why a wrapper can still reach your original function after the decorator's frame is gone, and why that original is not garbage collected even though no name points at it. See Session 02.

Decorator factory

A function you call to get a decorator, which is how a decorator takes arguments. In @repeat(2), repeat(2) runs first and returns the decorator that is then applied to your function.

functools.wraps

A decorator applied to a wrapper. It copies __name__, __qualname__, __doc__, __module__ and __annotations__ from the original function onto the wrapper, so tracebacks and help() keep telling the truth.

__wrapped__

The attribute functools.wraps sets on a wrapper, pointing at the function underneath. It is how tools reach past a decorator to the real thing.

Concurrency

GIL (Global Interpreter Lock)

The single lock a thread must hold to execute Python bytecode in CPython. It exists because interpreter bookkeeping, reference counts above all, would be corrupted by unsynchronised threads. It's a property of CPython, not of the Python language. See Session 07.

Thread

A real operating-system thread, sharing the whole heap with its siblings. threading.Thread(...) only creates an object; the OS thread appears at .start(), which is when ident stops being None.

Switch interval

How long a thread may hold the GIL before the interpreter asks it to hand over: sys.getswitchinterval(), 5 ms by default. The request is only honoured at a safe point, chiefly a jump back to the top of a loop or a call into a function.

Race condition

A bug whose outcome depends on the order in which threads happen to run. The classic shape is read-modify-write: two threads read the same value, both compute from it, and the second write erases the first. The GIL doesn't prevent this. It only keeps single bytecodes indivisible.

Atomic

An operation that cannot be interrupted partway. In CPython one bytecode is atomic; one line of your source usually is not, because it compiles to several. If a switch can land in the middle, the operation is not atomic.

Lock

threading.Lock, an object only one thread may hold at a time. Wrapping a read-modify-write in with lock: makes it indivisible with respect to every other thread that uses the same lock. A thread waiting for a lock is in a different queue from a thread waiting for the GIL.

Deadlock

Two threads each holding a lock the other needs. Neither can ever proceed and no error is raised. The program just stops. Prevented by always acquiring locks in the same order, or by holding only one at a time.

CPU-bound / I/O-bound

The question that picks your tool. I/O-bound code is slow because it waits, and threads and asyncio overlap the waiting. CPU-bound code is slow because it computes, and only a second process (or a library that releases the GIL) adds a second core.

Process

A second operating-system process running a second interpreter, with its own heap and its own GIL. Two processes really do execute Python at the same instant. They share no objects at all, so anything passed between them is copied.

Pickling

Turning an object into bytes so it can cross a process boundary and be rebuilt on the other side. The result is an equal object at a different address, never the same object. Lambdas, open files, connections and locks cannot be pickled.

Start method

How multiprocessing creates a child: spawn, fork or forkserver. Check yours with mp.get_start_method(). Where the child re-imports your module (spawn is the default on Windows and macOS), the if __name__ == "__main__": guard is required or the child spawns its own child.

Event loop

The object asyncio uses to drive coroutines on a single thread. It keeps a list of tasks that are ready and a list of things being waited on, runs one task until it suspends, then picks the next.

Coroutine

What calling an async def function gives you: an object that has run none of its body, exactly like a generator object. It suspends at await, keeping its frame and locals, and the event loop resumes it later. Never awaiting one produces RuntimeWarning: coroutine ... was never awaited.

await (yield point)

The only place a coroutine can be suspended, and a word you typed, visible in the source. This is what makes asyncio cooperative: between two awaits nothing else runs, so no lock is needed. Threads, by contrast, are pre-empted wherever the interpreter allows.

Task

An object that drives one coroutine on the event loop. A coroutine on its own does nothing; asyncio.gather or create_task wraps it in a Task and hands it to the loop.

Free-threaded build

An optional CPython build with no GIL, added by PEP 703. It was experimental in 3.13, is officially supported in 3.14, and is separate from the default build, which still has one. Check with sys._is_gil_enabled(), which exists from 3.13 onwards. It makes CPU-bound threads scale. It doesn't fix any race condition, and it makes the ones you have easier to hit.

Exceptions

Exception

An ordinary instance of a class that inherits from BaseException. raise X evaluates X (usually a constructor call) and then starts the object climbing the call stack. It carries args, a __traceback__, and links to related exceptions. See Session 08.

Unwinding

What happens between raise and the handler: each frame without a matching except around the current line is popped, locals and all, just as on return, and the exception arrives in the caller at the line that made the call. If it runs out of frames, the traceback is printed and the process exits with status 1.

Traceback

The record of frames an exception has passed through, outermost first, reachable as exc.__traceback__ and walked with tb_next. The printed Traceback (most recent call last) is that chain written out one frame per entry; read it from the bottom, where the error and the raise site are.

Handler (try / except)

An except clause covering the line an exception arrived on. Clauses are tested in order with isinstance, so a clause catches the class it names and every subclass. The frame that catches keeps its desk; the name bound by as err is deleted automatically when the block ends, to break the cycle between the exception, its traceback and the frame.

finally

A block that runs on every way out of a try: a clean finish, a handled exception, an unhandled one, or a return. The pending result is parked while it runs and resumed afterwards. A return inside finally replaces that result and throws away an exception in flight. Python 3.14 warns about it.

Context manager

Any object whose class defines __enter__ and __exit__. with EXPR as name: calls __enter__ and binds its return value to name, runs the body inside an invisible try, and then calls __exit__(exc_type, exc, tb), with three Nones on success. If __exit__ returns a true value the exception is suppressed. @contextlib.contextmanager builds one from a generator: the code before yield is the enter half, after it the exit half.

Exception chaining

An exception raised while another is being handled gets __context__ set to the one being handled; the traceback then says During handling of the above exception, another exception occurred. raise New from old also sets __cause__, and the sentence becomes The above exception was the direct cause of the following exception. from None hides the earlier one.

Exception hierarchy

Exceptions are classes with parents: KeyError and IndexError are LookupErrors, most things you raise yourself are Exceptions, and KeyboardInterrupt and SystemExit sit directly under BaseException so that except Exception lets a program be interrupted. A bare except: catches those too, which is why it is almost always a mistake.

Modules

Module

An ordinary object, an instance of the class module, made the first time a file is imported. Its __dict__ holds the file's top-level names, so config.TIMEOUT is a lookup in that dict, and a function defined in the file uses it as its globals. See Session 09.

import

Four moves. Look the name up in sys.modules. On a miss, find the file on sys.path, create a module object, register it, and run the file with the module's __dict__ as its globals. Then bind a name in the namespace that ran the import. A module's body runs once per process, however many files import it.

sys.modules

The import cache: a dict from module names to module objects. A module goes in before its body runs, which is how a circular import can find it half built, and comes out again if the body fails. A second import of the same name is one lookup here and runs nothing.

from ... import

from config import retries imports config, then binds retries in your namespace to whatever config.retries points at right then, much like retries = config.retries. It doesn't bind config, and it doesn't follow later rebinding inside the module. Mutating a shared object is still visible through both names.

__name__ and the main guard

__name__ is set before a module's body runs: "__main__" for the script you started (or ran with python -m), and the module's own name when it is imported. if __name__ == "__main__": runs a block only when the file is the program.

Circular import

Two modules that import each other. The second import finds the first module in sys.modules before its body has finished, so reading a name it hasn't defined yet raises AttributeError, or ImportError: cannot import name for a from-import. Read the names inside functions instead, or move them into a third module.

sys.path

The list of folders import searches, in order, after the modules built into the interpreter. Entry 0 is the folder of the script you ran, so a file there with a library's name (random.py, requests.py) hides the real one from the whole program. module.__file__ tells you which file you got.

Package

A module that can hold submodules, usually a folder with an __init__.py. import shop.orders runs shop/__init__.py first, then shop/orders.py, caches both, and binds the name shop. The package's __path__ lists the folders its submodules are searched in.

__pycache__

The folder where Python saves compiled bytecode for imported modules, as files like config.cpython-313.pyc, so the next start can skip compiling when the source hasn't changed. It speeds up start-up, not running time, and it is safe to delete.

importlib.reload

Runs a module's file again into the same module object, so code that reads config.name sees the new values. Names copied out earlier with from-imports, and instances of the old classes, still point at the old objects, which is why restarting is often simpler.