ewong718
ewong718

Reputation: 143

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

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?

Upvotes: 7

Views: 1389

Answers (5)

muddyfish
muddyfish

Reputation: 3650

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.

Upvotes: 9

Songy
Songy

Reputation: 851

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

Upvotes: 1

Ashwini Chaudhary
Ashwini Chaudhary

Reputation: 250971

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>

Upvotes: 1

Anand S Kumar
Anand S Kumar

Reputation: 90909

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) .

Upvotes: 1

tobspr
tobspr

Reputation: 8376

Whenever you call iter(x) you will get a new instance of an Iterator which will point to the same object. When you do a = iter(x) and b = iter(x) you have two iterators which point to one object. As the Iterator does not define __eq__() as equality of the object pointed to, but instead the iterator instance, even iter(x) == iter(x) will return false.

iter() basically calls the __iter__() function of the object:

Built-in Functions

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

Upvotes: 0

Related Questions