0

Consider the following minimum working example:

import itertools

for i in iter(itertools.count, 10):
    print(i)

I have expected that the output counts to 10. However, the output was count(0) over and over again. Printing the type instead gives <class 'itertools.count'>.

The documentation of iter(object, sentinel) says the following:

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.

Which reads to me like the behaviour I have expected. What have I overlooked? Optional bonus question: How is it possible with iter to make object a generator and get the expected behaviour?

user2009388
  • 179
  • 1
  • 11

1 Answers1

3

Fleshing out the docs for 2-arg iter, it works like:

def two_arg_iter(object, sentinel):
    while True:
        temp = object()
        if temp == sentinel:
            break
        yield temp

Which does exactly what you see it doing in the case you gave. Note:

>>> itertools.count()
count(0)

and count(0) is never equal to 10 so you keep getting count(0) forever.

It's not clear to me what you're asking, though. Either of these, I believe, would do what you actually want:

for i in itertools.count(10):

or, redundantly,

for i in iter(itertools.count(10)):

It may help to realize that 2-arg iter is a very different beast than 1-arg iter.

Tim Peters
  • 67,464
  • 13
  • 126
  • 132
  • Great explanation. TIL the 2-arg iter. Not sure this is what he wants: - break after print ten? for i in count(0): print(i); if i == 10: break; – Daniel Hao Dec 26 '20 at 04:51
  • 2
    @DanielHao Yeah, "counts to 10" sounds to me like they meant `iter(itertools.count().__next__, 10)`. – Kelly Bundy Dec 26 '20 at 05:01
  • Wow! Did not know I've bumped into an expert by Christmas night... are you the Timsort hybrid sorting designer?.... – Daniel Hao Dec 26 '20 at 05:02
  • 1
    Together with an [answer of a related question](https://stackoverflow.com/a/21982504), it seems that I have confused *generator functions* with *generator iterators*, and overlooked where exactly the `__next__()` method is called. Thanks for the detailed answer! As to why: I wanted to iterate through a generator without resorting to a `break`. Using `two_arg_iter()` seemed more pythonic, because it was even suggested in the docs for this case. Now that I would have to pass the `__next__()` method (thanks @KellyBundy), it seems more like a hacky workaround. – user2009388 Dec 26 '20 at 17:02
  • 2
    In that case, a variant of `itertools.takewhile(lambda i: i != 10, itertools.count())` may sufficiently hide the hacky part ;-) – Tim Peters Dec 26 '20 at 17:31