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

iterator, python

Solution

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

Problem

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.

Original source