Functional patterns & iterators
Senioritertools internals, iterator vs iterable and the __getitem__ fallback, and functools.reduce vs a plain loop.
Created 2 September 2026 · Updated 23 October 2026
4 concepts · detail hidden
01`itertools.chain` from scratch, and why `itertools` is written in C beyond raw speed
#c-itertoolschain-from-scratch-and-why-itertools-is-written-in-itertools.chain(*iterables)conceptually just yields every element from the first iterable, then every element from the second, and so on — trivially implementable as a Python generator (for it in iterables: yield from it). The reasonitertools’s actual implementations are written in C rather than pure Python matters beyond a generic “C is faster” — two specific, concrete advantages: constant memory overhead per item, since a C-level iterator avoids allocating a full Python generator frame object for the iteration itself, and no per-item Python bytecode dispatch overhead — every step of a pure-Python generator has to go through the bytecode interpreter’s per-instruction overhead, whereas a C implementation executes as compiled machine code with no such per-step interpretation cost. This becomes genuinely relevant, not just theoretically interesting, when chaining/transforming very large iterables where the per-item overhead, multiplied across millions of items, adds up to a real, measurable difference.02Iterator vs iterable — and why `__iter__` must return a fresh iterator for multi-pass support
#c-iterator-vs-iterable-and-why-__iter__-must-return-a-fresh-itAn iterable is anything you can call
iter()on to get an iterator (i.e., defines__iter__); an iterator is the object actually doing the stepping, defining__next__(and typically also__iter__returning itself, so an iterator is trivially also iterable). A common mistake: writing__iter__to simplyreturn self._some_generator, whereself._some_generatoris a single generator object created once (e.g., in__init__) and stored — this works for exactly one full pass, but a secondforloop over the same object gets that same, now-exhausted generator back, silently producing zero items instead of a fresh pass. The correct pattern:__iter__should create and return a brand-new generator/iterator every time it’s called (e.g.,def __iter__(self): return (x for x in self._data)— a fresh generator expression evaluated fresh on each call), so every independentforloop over the object gets its own independent iteration state.03The `__getitem__`-only iteration fallback — why `for` works without `__iter__`
#c-the-__getitem__-only-iteration-fallback-why-for-works-withouYes, it does — Python has a genuine, supported fallback iteration mechanism: if a class defines no
__iter__at all,forfalls back to repeatedly calling__getitem__(0), then__getitem__(1), and so on, incrementing the index each time, until anIndexErroris eventually raised (at which point the loop naturally stops) — this is precisely how sequences supported iteration in Python before__iter__existed as a dedicated protocol, and it remains a genuinely supported fallback today. Worth explicitly recognizing this is a fallback and not the primary, recommended mechanism — writing an explicit__iter__remains genuinely better modern practice — but knowing this fallback exists demystifies why certain older or unusual classes seem to mysteriously supportforiteration despite never defining__iter__at all.04`functools.reduce` vs a plain loop — readability, and its purpose-built alternatives
#c-functoolsreduce-vs-a-plain-loop-readability-and-its-purpose-reduce(function, iterable)repeatedly applies a two-argument function cumulatively across an iterable’s items, collapsing it down to a single value (e.g., reducing a list of numbers to their product). Guido van Rossum (Python’s creator) has been on record dislikingreducespecifically for readability reasons — for anything beyond the simplest, most obviously-associative operations (sum, product), areducecall with a nontrivial lambda tends to be genuinely harder to read at a glance than the equivalent explicit loop, since you have to mentally simulate the accumulation to understand what it’s doing, whereas a loop’s intent is usually visible line-by-line. It survived into Python 3 but was demoted from a builtin to living infunctools, signaling exactly this “use sparingly, prefer alternatives” stance. In practice, the specific common casesreduceis used for usually have clearer, purpose-built alternatives:sum()for addition,math.prod()for multiplication, anditertools.accumulate()when you actually want the running intermediate values (not just the final result).