The Python iter() function is the universal gateway into every for loop, list comprehension, and iterable-consuming built-in function in the language. Every for loop you write, every list comprehension you construct, and every call to a built-in function that consumes iterables begins by calling iter() on the object you supply. Despite its central role, iter() is often invisible to beginners because the for loop calls it implicitly. Understanding iter() explicitly gives you fine-grained control over how and when iteration begins, and it opens up patterns that are not possible with for loops alone, including consuming data from callable sources, reading until a sentinel value appears, and introspecting whether an object supports iteration before you attempt to traverse it.
The iter() function has two distinct calling forms that serve different purposes. The single-argument form, iter(iterable), takes any iterable object and returns a new iterator that can traverse it. This is the form that for loops use internally. The two-argument form, iter(callable, sentinel), takes a zero-argument callable and a sentinel value, and it returns an iterator that calls the callable repeatedly until the callable returns the sentinel, at which point the iterator raises StopIteration. This second form is less well known but is the cleanest way to consume data from files, sockets, queues, and any other source that uses a special value to mark the end of input.
The single-argument form: iter(iterable)
When you call iter() with a single argument, Python first checks whether the object has an iter() method. If it does, Python calls that method and returns the result, which must be an iterator object. If the object does not have iter(), Python checks for a getitem() method that accepts integer indexes starting from zero. If that method exists, Python constructs a built-in iterator object that calls getitem(0), getitem(1), getitem(2), and so on, stopping when IndexError is raised.
This fallback to getitem() is a backward-compatibility mechanism that supports older classes designed before the iterator protocol was formalized. You rarely need to rely on it in new code, but it explains why you can iterate over some objects that do not explicitly define iter(). Any class that supports integer indexing with square brackets and raises IndexError when the index is out of range is automatically iterable through this fallback path.
For built-in collection types, iter() returns a dedicated iterator object that is optimized for that type. Calling iter() on a list returns a list iterator. Calling it on a tuple returns a tuple iterator. Calling it on a dictionary returns a dictionary key iterator. Each of these iterator types is a distinct C-level object that implements the same Python iterator protocol but does so with type-specific internal logic that is faster than a generic Python implementation.
One important and often surprising behaviour of iter() is that calling it on an iterator returns the iterator itself. This is because an iterator's iter() method is defined to return self, as required by the Python iterator protocol. This means iter() is idempotent on iterators: iter(iter(x)) is equivalent to iter(x) when x is already an iterator. This property is what allows functions that accept iterables to call iter() unconditionally on their arguments without first checking whether the argument is already an iterator.
The single-argument form also raises TypeError if the object is not iterable. This exception is Python's way of telling you that the object lacks both iter() and a suitable getitem() method. Numbers, None, and most user-defined classes that have not been equipped with iteration support will trigger this error. You can use this behaviour to test whether an object is iterable by calling iter() inside a try-except block, though the recommended approach for most code is to use isinstance(obj, collections.abc.Iterable) from the collections.abc module.
The two-argument form: iter(callable, sentinel)
The two-argument form of iter() is one of Python's lesser-known built-in features, but it solves a common problem elegantly. When you are consuming data from an external source, the source often signals the end of available data by returning a special sentinel value. A file's readline() method returns an empty string at end of file. A queue's get() method might return None when the queue is closed. A socket's recv() method returns an empty bytes object when the connection is closed. The two-argument form of iter() wraps these patterns into a clean iterator that you can use in a for loop.
The callable argument must be a function or other callable object that accepts zero arguments. Python calls this callable repeatedly, each time passing its return value to the iterator consumer. When the callable returns a value that compares equal to the sentinel, the iterator raises StopIteration instead of yielding that value. The sentinel value itself is never produced by the iterator.
The most common use of this form is reading lines from a file until the end:
with open("data.txt") as f:
for line in iter(f.readline, ""):
print(line.strip())Each iteration calls f.readline(), which returns the next line or an empty string at EOF. When readline() returns an empty string, the iterator raises StopIteration, and the loop exits. The empty string that signals EOF is consumed by iter() and never passed to the loop body. This pattern is cleaner than a while loop with a break condition because the iteration logic and the termination logic are combined into a single expression.
Another practical use is consuming items from a queue until a shutdown signal arrives. If a worker thread receives tasks from a queue and the producer sends None to indicate that no more tasks are coming, the worker can use iter() with a sentinel:
import queue
task_queue = queue.Queue()
def process_tasks():
for task in iter(task_queue.get, None):
print(f"handling {task}")The loop calls task_queue.get() for each iteration, waiting for a task if the queue is empty. When the producer pushes None, the iterator stops, and the loop exits. The None sentinel is consumed by iter() and never reaches the handle() function.
How iter() integrates with Python's data model
The iter() function is not magic. It is a thin wrapper around Python's internal C function PyObject_GetIter(), which implements the logic described above: try iter(), fall back to getitem() with sequential indexes, and raise TypeError if neither works. This means any class you write can control how iter() behaves by implementing the appropriate special methods.
If your class defines iter(), iter() calls it and returns the result. The result must be an object that implements the iterator protocol. If your class does not define iter() but defines getitem(), iter() constructs a default iterator that calls getitem(0), getitem(1), and so on. If your class defines neither, iter() raises TypeError.
The two-argument form does not call any special methods on the callable or the sentinel beyond ordinary comparison. It simply calls the callable repeatedly in a loop and checks whether each return value is equal to the sentinel, using the same equality rules as the == operator. This is why string sentinels like an empty string work naturally: any empty string returned by the callable matches the sentinel, regardless of whether it is the same object. When you need a sentinel that can never collide with real data, the common pattern is to create a unique marker with sentinel = object(). A fresh object() compares equal only to itself, so no legitimate value from the callable can accidentally terminate the iteration.
Practical patterns with iter()
The iter() function enables several patterns that make Python code more readable and more idiomatic. One such pattern is using iter() to safely test whether an object is iterable before attempting to loop over it. While the recommended approach is isinstance(obj, Iterable), you can also use iter() directly:
try:
iterator = iter(obj)
except TypeError:
print(f"{type(obj).__name__} is not iterable")
else:
for item in iterator:
print(item)This pattern is useful when you are writing generic functions that accept either a single value or a collection of values. By attempting iter() and catching TypeError, you can branch between treating the argument as a single item and treating it as a collection to iterate over.
Another pattern is using iter() to create an iterator that you can manually advance with Python's next() function. This is useful when you need to peek at the next item without consuming it, when you need to skip items based on a condition, or when you are implementing a state machine that consumes items from an iterator at its own pace.
A third pattern involves the two-argument form for reading fixed-size chunks from a binary stream. If you have a partial function that reads exactly N bytes from a file, you can iterate until the empty bytes sentinel:
from functools import partial
with open("data.bin", "rb") as f:
reader = partial(f.read, 1024)
for chunk in iter(reader, b""):
print(f"read {len(chunk)} bytes")Each iteration reads up to 1024 bytes. When read() returns an empty bytes object, the iterator stops. The 1024-byte chunks are never all in memory at once, which means this pattern can process files of any size with a fixed memory footprint.
The relationship between iter() and for loops
Every for loop in Python begins by calling iter() on the object that follows the in keyword. The code for item in collection is equivalent to calling iter(collection) to obtain an iterator, then calling next() on that iterator in a loop until StopIteration is raised. The for loop handles all of this internally, which is why you rarely see explicit iter() calls in everyday Python code.
But understanding this equivalence helps you debug iteration problems. If a for loop raises TypeError claiming that an object is not iterable, the problem is that iter() failed on that object. If a for loop runs zero times when you expected it to run, the problem is that iter() returned an iterator that immediately raised StopIteration on the first next() call. If a for loop runs forever, the problem is that the iterator's next() method never raises StopIteration.
You can verify the for-loop equivalence by rewriting any for loop as a while loop with explicit iter() and next() calls. The two forms produce identical output and have identical exception behaviour. The for loop is shorter and harder to get wrong, but the while loop version makes the mechanism visible, which is valuable when you are learning how iteration works or debugging an iteration problem.
iter() and custom objects
When you design a class that should work with Python's iteration tools, implementing the special methods that iter() relies on is the single most impactful thing you can do. A class with a correct iter() method works with for loops, comprehensions, tuple() and list() constructors, sum(), min(), max(), sorted(), any(), all(), zip(), map(), filter(), and every other function in the standard library that accepts an iterable.
Implementing iter() is also the gateway to more advanced patterns. Once your class is iterable, you can pass instances of it to create custom iterators in Python that traverse your data in different orders or with different filters. You can pass them to generator functions that transform the iteration. You can combine them with itertools to build data processing pipelines. The iter() function is the entry point, and once your class works with iter(), the entire iteration ecosystem is available to you.
Rune AI
Key Insights
- iter() in its single-argument form returns an iterator from any iterable, calling iter() or falling back to getitem() with sequential indexes.
- iter() in its two-argument form creates an iterator from a callable and a sentinel value, stopping when the callable returns the sentinel.
- The two-argument form is ideal for consuming streams, files, sockets, and queues where a special value signals the end of data.
- Calling iter() on an iterator returns the iterator itself, which is why iterators can be used wherever iterables are expected.
- Every Python for loop calls iter() implicitly, which means understanding iter() helps you understand the mechanism behind every loop you write.
Frequently Asked Questions
What does the Python iter() function do?
What happens if I call iter() on something that is not iterable?
When should I use the two-argument form of iter()?
Conclusion
The iter() function is the entry point to Python's entire iteration system. Every for loop, list comprehension, and iterable-consuming built-in function begins by calling iter() on its input. Understanding both the single-argument factory form and the two-argument sentinel form gives you the tools to create iterators from any data source, whether it is an in-memory collection or a live external stream.
More in this topic
Create Custom Iterators in Python
Learn how to build your own iterators in Python by implementing the __iter__() and __next__() methods, with practical examples and reusable patterns.
Python Iterator Protocol Explained
Learn how Python's iterator protocol works with the __iter__() and __next__() methods, and understand the contract every iterator must follow.
Python Iterables and Iterators Explained
Learn what iterables and iterators are in Python, how they power for loops under the hood, and why understanding them unlocks cleaner data processing patterns.