nums = [1, 2, 3] # iterable
it = iter(nums) # iterator
print(next(it)) # 1
print(next(it)) # 2
Topics
20
Async Generators & Async Context Managers
Command-Line Interfaces (argparse)
Comprehensions & Generators
Concurrency (asyncio/threading/multiprocessing)
Data Types & Structures
Dataclasses & NamedTuples
Debugging & Profiling
Decorators
Descriptors & Properties
Exception Handling
File I/O & Context Managers
Functions & Scope
functools & Functional Programming Tools
Iterators & the Iterator Protocol
Logging
Magic Methods & Operator Overloading
Memory Management & Garbage Collection
Metaclasses & Class Customization
Modules & Packaging
Multiple Inheritance & MRO
Iterators & the Iterator Protocol
15 questions found
An iterable is any object implementing __iter__() that returns an iterator, such as lists, tuples, and dicts -- you can loop over it repeatedly. An iterator is an object implementing both __iter__() (returning itself) and __next__() (returning the next value or raising StopIteration) -- it's stateful and exhausted after one full pass.
Real-world example
A file object is an iterator over its lines -- you can only read through it once before needing to reopen or seek back to the start.
Comprehensions & Generators;Magic Methods & Operator Overloading
A custom iterator class defines __next__() to return the next value or raise StopIteration when exhausted, and __iter__() to return self so the object works directly in for-loops and with the built-in iter() function.
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
self.current -= 1
return self.current + 1
for n in Countdown(3):
print(n) # 3 2 1
Real-world example
A custom paginated API client implements __next__ to fetch and yield the next page of results lazily as the caller iterates.
Iterators & the Iterator Protocol;Comprehensions & Generators
StopIteration signals that an iterator has no more values. A for-loop repeatedly calls next() on the iterator behind the scenes and automatically catches StopIteration to end the loop cleanly, without the exception propagating visibly to your code.
it = iter([1, 2])
while True:
try:
print(next(it))
except StopIteration:
break
Real-world example
Understanding this lets you manually drive an iterator with next() and a try/except when you need finer control than a for-loop provides, such as peeking ahead.
Async Generators & Async Context Managers;Exception Handling
iter(callable, sentinel) repeatedly calls callable with no arguments and yields each result until the result equals sentinel, at which point iteration stops. This is useful for turning polling-style APIs (like reading fixed-size chunks or reading until a marker) into a clean iterator.
with open('data.bin', 'rb') as f:
for chunk in iter(lambda: f.read(1024), b''):
process(chunk)
Real-world example
A file-reading loop uses iter(f.read_chunk, '') to process a stream in fixed-size blocks until an empty read signals end-of-file.
File I/O & Context Managers;Iterators & the Iterator Protocol
What is the difference between __iter__ returning self versus returning a separate iterator object?
AdvancedIf a class's __iter__ returns self, the object is both iterable and iterator, meaning it can only be iterated once -- a second for-loop over the same instance starts from wherever the first left off (likely exhausted). If __iter__ returns a fresh iterator object each time, the class is a reusable iterable that supports multiple independent iteration passes, which is the more robust and expected design for container-like classes.
class ReusableRange:
def __init__(self, n):
self.n = n
def __iter__(self):
return iter(range(self.n)) # fresh iterator each call
r = ReusableRange(3)
print(list(r), list(r)) # both give [0, 1, 2]
Real-world example
A custom Dataset class in a data pipeline returns a new iterator from __iter__ each epoch so training can loop over the same dataset multiple times.
Comprehensions & Generators;Data Types & Structures
itertools provides fast, memory-efficient building blocks that operate on the iterator protocol: infinite iterators (count, cycle, repeat), combinatoric generators (product, permutations, combinations), and terminating iterators (chain, islice, groupby, takewhile). They let you compose complex lazy pipelines without materializing intermediate lists.
import itertools
for combo in itertools.combinations([1,2,3], 2):
print(combo) # (1,2) (1,3) (2,3)
for x in itertools.islice(itertools.count(10), 3):
print(x) # 10 11 12
Real-world example
A test suite uses itertools.product to generate every combination of browser, OS, and screen resolution for cross-browser test cases without nested loops.
functools & Functional Programming Tools;Comprehensions & Generators
How would you implement a lazy, chainable iterator pipeline (like filter-then-map) from scratch?
AdvancedYou can chain iterators by wrapping one iterator's __next__ around another's, pulling values on demand and applying transformations lazily rather than eagerly. Each stage only computes a value when the next stage requests it, keeping memory usage constant regardless of input size.
class MapIter:
def __init__(self, it, fn):
self.it, self.fn = it, fn
def __iter__(self):
return self
def __next__(self):
return self.fn(next(self.it))
pipeline = MapIter(iter([1,2,3]), lambda x: x*10)
print(list(pipeline)) # [10, 20, 30]
Real-world example
A log-processing tool chains custom filter and transform iterators over a multi-gigabyte log file so only one line is ever held in memory at a time.
Comprehensions & Generators;Concurrency (asyncio/threading/multiprocessing)
next(iterator, default) returns default instead of raising StopIteration when the iterator is exhausted. This avoids needing a try/except block when you just want a fallback value for an empty or finished iterator.
empty = iter([])
print(next(empty, 'no items')) # 'no items' instead of StopIteration
Real-world example
A search function uses next((x for x in items if x.match), None) to get the first matching item or None without writing an explicit loop.
Exception Handling;Comprehensions & Generators
How does Python know whether to treat a for-in target as iterable, and what error occurs if it isn't?
IntermediateThe for-loop calls iter() on the target, which invokes __iter__(). If the object has no __iter__ (and, as a fallback, no __getitem__ supporting integer indexing from 0), Python raises TypeError: 'X' object is not iterable. This is why custom classes need __iter__ (or, historically, __getitem__) to support iteration.
class NoIter:
pass
try:
for x in NoIter():
pass
except TypeError as e:
print(e) # 'NoIter' object is not iterable
Real-world example
A bug report of 'object is not iterable' on a custom API response wrapper leads to adding __iter__ that delegates to an internal list attribute.
Magic Methods & Operator Overloading;Data Types & Structures
How does itertools.tee let you iterate over the same iterator multiple times independently?
Advancedtee(iterable, n) splits a single iterator into n independent iterators that can be advanced separately, internally buffering values that one consumer has read but another hasn't yet. This is essential because a normal iterator is single-pass and consumed once read; tee avoids materializing the whole sequence into a list just to reuse it, though it does use memory proportional to how far apart the consumers get.
import itertools
it = iter([1,2,3,4])
a, b = itertools.tee(it, 2)
print(next(a)) # 1
print(list(b)) # [1,2,3,4]
print(list(a)) # [2,3,4]
Real-world example
A streaming analytics job uses tee to feed the same event stream into both a real-time alerting consumer and a slower batch-aggregation consumer.
functools & Functional Programming Tools;Memory Management & Garbage Collection
Showing 1–10 of 15