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