Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Second argument of the iter() method

I'm trying to figure out how to make iterator, below is an iterator that works fine.

class DoubleIt:

    def __init__(self):
        self.start = 1

    def __iter__(self):
        self.max = 10
        return self

    def __next__(self):
        if self.start < self.max:
            self.start *= 2
            return self.start
        else:
            raise StopIteration

obj = DoubleIt()
i = iter(obj)
print(next(i))

However, when I try to pass 16 into the second argument in iter() (I expect the iterator will stop when return 16)

i = iter(DoubleIt(), 16)
print(next(i))

It throws TypeError: iter(v, w): v must be callable Therefore, I try to do so.

i = iter(DoubleIt, 16)
print(next(i))

It returns <main.DoubleIt object at 0x7f4dcd4459e8>. Which is not I expected. I checked the website of programiz, https://www.programiz.com/python-programming/methods/built-in/iter Which said that callable object must be passed in the first argument so as to use the second argument, but it doesn't mention can User defined object be passed in it in order to use the second argument.

So my question is, is there a way to do so? Can the second argument be used with the "Self defined Object"?

like image 928
Stephen Fong Avatar asked Jun 14 '18 09:06

Stephen Fong


People also ask

What does ITER () do in Python?

python iter() method returns the iterator object, it is used to convert an iterable to the iterator. Parameters : obj : Object which has to be converted to iterable ( usually an iterator ). sentinel : value used to represent end of sequence.

What does __ Iter__ method returns?

The __iter__() method returns the iterator object itself. If required, some initialization can be performed. The __next__() method must return the next item in the sequence. On reaching the end, and in subsequent calls, it must raise StopIteration .

Which methods are defined in an iterator class ITER next?

An iterator is an object that contains a countable number of values. An iterator is an object that can be iterated upon, meaning that you can traverse through all the values. Technically, in Python, an iterator is an object which implements the iterator protocol, which consist of the methods __iter__() and __next__() .

How do I make a list iterable in Python?

For example, a list is iterable but a list is not an iterator. An iterator can be created from an iterable by using the function iter(). To make this possible, the class of an object needs either a method __iter__, which returns an iterator, or a __getitem__ method with sequential indexes starting with 0.


Video Answer


2 Answers

The documentation could be a bit clearer on this, it only states

iter(object[, sentinel])

...

The iterator created in this case will call object with no arguments for each call to its __next__() method; if the value returned is equal to sentinel, StopIteration will be raised, otherwise the value will be returned.

What is maybe not said perfectly clearly is that what the iterator yields is whatever the callable returns. And since your callable is a class (with no arguments), it returns a new instance of the class every iteration.

One way around this is to make your class callable and delegate it to the __next__ method:

class DoubleIt:

    def __init__(self):
        self.start = 1

    def __iter__(self):
        return self

    def __next__(self):
        self.start *= 2
        return self.start

    __call__ = __next__

i = iter(DoubleIt(), 16)
print(next(i))
# 2
print(list(i))
# [4, 8]

This has the dis-/advantage that it is an infinite generator that is only stopped by the sentinel value of iter.

Another way is to make the maximum an argument of the class:

class DoubleIt:

    def __init__(self, max=10):
        self.start = 1
        self.max = max

    def __iter__(self):
        return self

    def __next__(self):
        if self.start < self.max:
            self.start *= 2
            return self.start
        else:
            raise StopIteration

i = iter(DoubleIt(max=16))
print(next(i))
# 2
print(list(i))
# [4, 8, 16]

One difference to note is that iter stops when it encounters the sentinel value (and does not yield the item), whereas this second way uses <, instead of <= comparison (like your code) and will thus yield the maximum item.

like image 144
Graipher Avatar answered Oct 20 '22 08:10

Graipher


Here's an example of a doubler routine that would work with the two argument mode of iter:

count = 1
def nextcount():
    global count
    count *= 2
    return count

print(list(iter(nextcount, 16)))
# Produces [2, 4, 8]

This mode involves iter creating the iterator for us. Note that we need to reset count before it can work again; it only works given a callable (such as a function or bound method) that has side effects (changing the counter), and the iterator will only stop upon encountering exactly the sentinel value.

Your DoubleIt class provided no particular protocol for setting a max value, and iter doesn't expect or use any such protocol either. The alternate mode of iter creates an iterator from a callable and a sentinel value, quite independent of the iterable or iterator protocols.

The behaviour you expected is more akin to what itertools.takewhile or itertools.islice do, manipulating one iterator to create another.

Another way to make an iterable object is to implement the sequence protocol:

class DoubleSeq:
    def __init__(self, steps):
        self.steps = steps
    def __len__(self):
        return self.steps
    def __getitem__(self, iteration):
        if iteration >= self.steps:
            raise IndexError()
        return 2**iteration

print(list(iter(DoubleSeq(4))))
# Produces [1, 2, 4, 8]

Note that DoubleSeq isn't an iterator at all; iter created one for us using the sequence protocol. DoubleSeq doesn't hold the iteration counter, the iterator does.

like image 22
Yann Vernier Avatar answered Oct 20 '22 09:10

Yann Vernier