2025, Sep 23 15:00
Is vobject.readComponents a Generator Function or a Generator Iterator? Clearing Up Python Docs
Learn why vobject.readComponents is a generator function returning a generator iterator. Avoid restarts: bind iterator once; mix next() and for loops safely.
When documentation says a function “is a generator,” does it mean the function itself is a generator or that it returns one? This nuance shows up in vobject’s readComponents: the docs describe it as a generator, while the examples clearly use it as something you iterate over or call next() on. Clarifying this saves time and avoids subtle iteration mistakes.
The setup
The documentation describes readComponents as a generator with wording like “Generate one Component at a time from a stream.” At the same time, examples use it in ways that imply it returns a generator iterator, such as calling next() or using it directly in a for-loop.
Code that triggers the confusion
Consider two common usage patterns side by side. Both rely on the same behavior, but they read differently if you’re unsure what “generator” refers to.
from vobject import readComponents as emit_parts
# Access a single value via next()
cal_src = ical_stream_source
first_dt = next(emit_parts(cal_src)).vevent.dtstart.value
# Iterate over all components
vcf_source = vcf_stream_source
for comp in emit_parts(vcf_source):
    handle(comp)
If you repeatedly call a function that returns a generator iterator, you recreate a fresh iterator each time. That matters when you expect to continue where you left off.
What “generator” means here
The term “generator” in Python can refer to either a generator function or a generator iterator (also called a generator object). Calling a generator function produces a new generator iterator each time, and each iterator can be consumed only once. This aligns with the Python glossary’s wording:
A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function.
Usually refers to a generator function, but may refer to a generator iterator in some contexts. In cases where the intended meaning isn’t clear, using the full terms avoids ambiguity.
Applied to vobject.readComponents: it is a generator in the sense of a generator function, and it returns a generator iterator. It’s not very common for a module to contain generator iterators directly, so in documentation, it’s reasonable to assume that “generator” refers to the function.
The fix in practice
If you want to iterate once and keep your place, bind the returned generator iterator to a variable and use it consistently, instead of calling the function repeatedly.
from vobject import readComponents as emit_parts
cal_src = ical_stream_source
items_iter = emit_parts(cal_src)
first_dt = next(items_iter).vevent.dtstart.value
for comp in items_iter:
    handle(comp)
This way you don’t create a new iterator inadvertently, and you consume the stream exactly once, in order.
Why this nuance matters
Relying on the function-versus-iterator distinction helps avoid accidental restarts of a stream and prevents confusion when combining next() with a for-loop. It also sets expectations: each call to the generator function produces an independent, one-pass iterator, and that iterator can only be consumed once.
Wrap-up
When documentation calls something a “generator,” read it as shorthand for a generator function unless the context explicitly points to a generator iterator. For vobject.readComponents, treat it as a function that returns a generator iterator. If you plan to advance through a stream over time, store the iterator and iterate consistently. If you need a fresh pass, call the function again to get a new iterator.
The article is based on a question from StackOverflow by user2153235 and an answer by Anerdw.