Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between iter(x) and x.__iter__()?

Tags:

python

What is the difference between iter(x) and x.__iter__()?

From my understanding, they both return a listiterator object but, in the below example, I notice that they are not equal:

x = [1, 2, 3]
y = iter(x)
z = x.__iter__()
y == z
False

Is there something that I am not understanding about iterator objects?

like image 987
ewong718 Avatar asked Jul 23 '15 14:07

ewong718


4 Answers

Iter objects dont have equality based on this sort of thing.

See that iter(x) == iter(x) returns False as well. This is because the iter function (which calls __iter__) returns an iter object that doesnt overload __eq__ and therefore only returns True when the 2 objects are the same.

Without overloading, == is the same as the is comparison.

Also, x.__iter__().__class__ is iter(x).__class__ showing that, in this case, they return the same type of object.

like image 128
muddyfish Avatar answered Nov 05 '22 12:11

muddyfish


They are not completely the same always. From documentation -

iter(o[, sentinel])

Return an iterator object. The first argument is interpreted very differently depending on the presence of the second argument. Without a second argument, o must be a collection object which supports the iteration protocol (the __iter__() method), or it must support the sequence protocol (the __getitem__() method with integer arguments starting at 0).

But for your list case, they are similar, as in iter() internally calls __iter__() . But they both return different iterator objects, you can iterate both returned value for both separately, and hence they are not equal.

Example to show general case of iterators -

In [13]: class CA:
   ....:     def __iter__(self):
   ....:         print('Inside __iter__')
   ....:         return iter([1,2,3,4])
   ....:

In [14]: c = CA()

In [15]: iter(c)
Inside __iter__
Out[15]: <list_iterator at 0x3a13d68>

In [16]: c.__iter__()
Inside __iter__
Out[16]: <list_iterator at 0x3a13908>    #see that even the ids are different for the list_iterator objects.

In [17]: class BA:
   ....:     def __getitem__(self,i):
   ....:         print('Inside __getitem__')
   ....:         return i+5
   ....:

In [18]: b = BA()

In [19]: iter(b)
Out[19]: <iterator at 0x3a351d0>

In [20]: x = iter(b)

In [21]: next(x)
Inside __getitem__
Out[21]: 5

In [23]: next(x)
Inside __getitem__
Out[23]: 6

Example to show that each call to iter() returns a different iterator object , that can be iterated upon separately -

In [24]: i = iter(c)
Inside __iter__

In [25]: j = iter(c)
Inside __iter__

In [26]: for x in i:
   ....:     pass
   ....:

In [27]: next(i)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-27-bed2471d02c1> in <module>()
----> 1 next(i)

StopIteration:

In [28]: next(j)
Out[28]: 1

As you can see above, even though i got exhausted, j was still at the starting position, so you can see both are completely different objects (with different states) .

like image 40
Anand S Kumar Avatar answered Nov 05 '22 11:11

Anand S Kumar


A list supports multiple iterators in Python, so calling iter() on it each time will return a new iterator object and they don't have their own __eq__ method, hence in the end Python ends up comparing them using their IDs, which is different of course.

>>> type(iter([])).__eq__
<method-wrapper '__eq__' of type object at 0x100646ea0>
>>> object.__eq__
<method-wrapper '__eq__' of type object at 0x100650830>

A class whose instances support only one iterator:

class A(object):
    def __iter__(self):
        return self

    def next(self):
        pass

a = A()
y = iter(a)
z = a.__iter__()
print y == z # will print True

It is possible to get True for different iterators as well but only if you return an iterator type that compare equal on comparison.

With that said never call iter(obj) like obj.__iter__(), because in this case instead of looking up for the __iter__ on the class it will look for __iter__ on the instance first:

class A(object):
    def __init__(self):
        self.__iter__ = lambda: iter(())
    def __iter__(self):
        return iter([])


a = A()
print a.__iter__()
print iter(a)
# output
<tupleiterator object at 0x10842b250>
<listiterator object at 0x10842b2d0>
like image 24
Ashwini Chaudhary Avatar answered Nov 05 '22 10:11

Ashwini Chaudhary


The is no difference between making an iterator through calling iter(x) or x.__iter__(). You are making 2 listiterator objects and comparing them. They are compared to see if they are the same iterator object not what they produce.

>>> test = [1,2,3,4]
>>> iter(test)
<listiterator object at 0x7f85c7efa9d0>
>>> test.__iter__()
<listiterator object at 0x7f85c7efaa50>

You can see that 2 different objects are produced.

It's the same if you call iter(test) twice too.

You can make them the same by pointing a two variables at the same object.

>>> test = [1,2,3,4]
>>> iter_one = iter(test)
>>> iter_two = iter_one
>>> print iter_one == iter_two
True
>>> iter_one.next()
1
>>> iter_two.next()
2

You can see they both reference the same object.

You can check if difference iterators produce the same output by converting them back to lists again.

>>> print list(iter(test)) == list(test.__iter__())
True
like image 33
Songy Avatar answered Nov 05 '22 10:11

Songy