SmCaterpillar
SmCaterpillar

Reputation: 7020

Using iter on an existing iterator in Python: What happens to the iterator?

The iter function wraps objects like lists or tuples in order to use them as iterators, i.e. one is able to use next, for example. For instance,

next(iter([1, 2, 3]))

returns 1.

What happens internally if the object we pass to iter is already an iterator? Does it simply return the original object, i.e. no-op? Or does it produce a new iterator wrapping the original one? And by wrapping I don't mean copying the original iterator, of course.

Upvotes: 5

Views: 396

Answers (2)

Veedrac
Veedrac

Reputation: 60227

Empirical evidence is nice, but the docs are pretty explicit.

Iterators are required to have an __iter__() method that returns the iterator object itself

If you implement an object with a __next__(), you should have an __iter__() method that returns self. Breaking this rule means you have an object that isn't an iterator but looks like one, which is a recipie for disaster.

Upvotes: 1

georg
georg

Reputation: 215049

TLDNR: iter returns obj.__iter_. It doesn't return obj "as is".

The Cpython implementation of iter is pretty straightforward:

PyObject *
PyObject_GetIter(PyObject *o)
{
    PyTypeObject *t = o->ob_type;
    getiterfunc f = NULL;
    if (PyType_HasFeature(t, Py_TPFLAGS_HAVE_ITER))
        f = t->tp_iter; // <- if it has __iter__, return that
    ....more stuff

So when you call iter(obj) and obj.__iter__ exists it just returns that. Most (all?) built-in iterators have __iter__ = self, e.g.

PyTypeObject PyListIter_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "listiterator",                             /* tp_name */
    ....
    PyObject_SelfIter,                          /* tp_iter */
    ....

but that's not necessary true for userland objects:

class X:
    def __iter__(self):
        return Y()

class Y:
    def __iter__(self):
        return iter('xyz')


a = iter(X())
b = iter(a)
print a is b # False

Upvotes: 3

Related Questions