0

I was looking for solutions to take multiline input in python. I found this answer which uses the following code.

sentinel = '' # ends when this string is seen
for line in iter(input, sentinel):
    pass # do things here

I read from the python docs that if iter recieves the second argument then it will call the __next__() of the first argument. But I don't think input has the __next__() implemented (I am not able to verify this either through the docs or surfing through the source code). Can someone explain how it's working?

Also, I observed this weird behaviour with the following code.

sentinel = ''
itr = iter(input, sentinel)
print("Hello")
print(set(itr))

Here is the output

[dvsingla Documents]$ python3 temp.py
Hello
lksfjal
falkja
 aldfj

{' aldfj', 'falkja', 'lksfjal'}
[dvsingla Documents]$ 

The prompt starts taking input after printing Hello which is not following line by line interpretation.

Thanks for any help

Dhruv
  • 117
  • 11
  • I believe the `input` in that code is not the built-in `input` function. Instead, someone chose an unfortunate name for a local variables, as in `input = "abcd"`. – zvone Jan 25 '23 at 07:55
  • 1
    Did you try reading the docs for [`iter`](https://docs.python.org/3/library/functions.html#iter)? – Mad Physicist Jan 25 '23 at 07:55
  • 1
    @zvone. No, that's the input function all right – Mad Physicist Jan 25 '23 at 07:56
  • @MadPhysicist Oh, that is even worse then :D Really ugly code in my opinion. – zvone Jan 25 '23 at 07:57
  • 1
    @zvone. Why? I think it's fine if you understand what it does – Mad Physicist Jan 25 '23 at 07:58
  • Your code is behaving exactly as the documentation says it will. The reference in the docs to \_\_next__ means *iter*'s \_\_next__ method and not the callable given as the first argument – DarkKnight Jan 25 '23 at 07:59
  • @MadPhysicist Oh well, it is just not explicit enough. Having a delayed/lazy evaluation of user interaction like that, using an iterator, just makes the code more difficult to understand. There is hardly ever a benefit which would justify it. – zvone Jan 25 '23 at 08:04
  • @zvone. It looks pretty clear if you know what the functions do. Admittedly the two-arg form of `iter` is a bit arcane, but it is clearly documented. – Mad Physicist Jan 25 '23 at 08:05

1 Answers1

1

iter has three forms: two one-arg and a two-arg version. The first one-arg version is the one that you are likely more familiar with.

  1. If object supports __iter__, iter will call that and return the result.

  2. If not, iter will look for __getitem__ (which is assumed to accept integers at that point) and __len__. If both are found in object, iter will instantiate its own internal iterator type that has a __next__ method which increments the index to object.__getitem__ calls until it reaches __len__.

  3. The two-arg form is much less used (and probably less known). It takes two arguments: object is a no-arg callable, and sentinel is a value that will be compared to what object returns to determine the end of iteration. In this case iter returns an instance of an internal class which supports __next__, just like for #2. Each call to __next__ will delegate to object until it returns sentinel. That's the version that you see being used here.

In all three cases, object itself does not need to implement __next__ directly. The iterator returned by iter does. For case #1, that's something object produces. For cases #2 and #3, is a wrapper made by iter.

The reason that the printouts happen as they do is that all forms of iter return a lazy iterator, not just the first form. itr will not execute anything until the call to set, which will repeatedly call input through itr.__next__(). This will continue until the user enters an empty line, which will be equal to the chosen sentinel.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • To get the next value, isn't it compulsory for `__next__()` to be implemented? The doc says , `If the second argument, sentinel, is given, then object must be a callable object. The iterator created in this case will call object with no arguments for each call to its __next__() method`. – Dhruv Jan 25 '23 at 08:38
  • Should an object have explicit `__next__()` method or are there other ways as well? – Dhruv Jan 25 '23 at 08:43
  • @Dhruv. The docs are saying that the iterator which is returned from that call to `iter` will have a `__next__` method, not the object you passed in. Each call to that `__next__` method will call the function you passed in as the first argument until it returns the sentinel value. It's important to read carefully. – Mad Physicist Jan 25 '23 at 09:35
  • Could you add the above in the answer as this was the main part of my question. I got confused regarding `__next__` due to to some examples on the internet. – Dhruv Jan 25 '23 at 10:06
  • @Dhruv. How's it look now? – Mad Physicist Jan 25 '23 at 15:56
  • @Dhruv. I didn't like the edits you made, but hopefully the update clarifies the parts you were having a problem with – Mad Physicist Jan 26 '23 at 12:25