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.
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
                        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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With