0

If I have an iterator foo and a function progress which progresses it some arbitrary amount (but with the guarantee that it will never raise StopIteration), how do I keep calling progress until foo is empty?

It looks like iter([]) is truthy.

This is my best attempt:

from itertools import islice
import copy

# Let's say I can't touch this function.
def progress(xs):
    islice(xs, 1) # or some other arbitrary value.

foo = iter(a_list)
while list(copy.copy(foo)): # 
    progress(foo)
sidney
  • 757
  • 1
  • 6
  • 10
  • 1
    Would you give a more complete code example, please? It is difficult to understand what you are trying to achieve without the definition of what `foo` is and how `progress` affects it. – DaLynX Dec 16 '21 at 13:03
  • If you want to put the contents of `foo` in a list until `foo` is exhausted, then just do `mylist = list(foo)`. Otherwise you'll need to explain more clearly what you're trying to do. – khelwood Dec 16 '21 at 13:06
  • I like the question, but it is still vague. Perhaps you post some example with what `progress` exactly does? – Chris Dec 16 '21 at 13:13
  • The DEFINITION of an iterator in Python is: "An iterator object implements `__next__`, which is expected to return the next element of the iterable object that returned it, and to raise a StopIteration exception when no more elements are available." So if your `progress()` function never attempts to get an item beyond the end of the iterator, what does it do instead when called and `foo` is empty? – vaizki Dec 16 '21 at 13:14
  • Good points. I've clarified in the description: `progress` uses `itertools.islice`, it seems like this is important. Sorry for being unclear. – sidney Dec 16 '21 at 13:24
  • The `progress()` function should return the number of items processed or at least True/False based on whether it got items or not. Otherwise you don't really have a way to detect if the iterable has more to give. Of course your iterator MAY have some way to detect it's exhausted but without seeing the code for it can't say. – vaizki Dec 16 '21 at 13:41

3 Answers3

1

Since you can't touch progress, you could wrap the iterator in another iterator that sets a flag when it's consumed, you then call progress in a while loop while the flag is not set. The same could be achieved by raising an exception, although this may interfere with the progress function.

Ideally you would be able to edit the progress method to get it to return something that can be used to detect when it's finished

from itertools import islice

foo = iter([1,2,3,4,5])

def progress(xs):
    list(islice(xs, 1))

finished = False

def wrapper(xs):
    for x in xs:
        yield x
    global finished
    finished = True

bar = wrapper(foo)

while not finished:
    progress(bar)
Iain Shelvington
  • 31,030
  • 3
  • 31
  • 50
  • I think this is the best solution with the new constrants in the question (can't change `progress()` implementation and no `StopIteration` raised). Instead of a generator function and global variable this could of course be also a custom iterator class. But only because I hate globals :) – vaizki Dec 17 '21 at 11:11
0

Are you looking for the following syntax?

for item in itemlist:
    do_something(item)
DaLynX
  • 328
  • 1
  • 11
  • No, I can't use a `for` loop over the iterator (AFAICT) because I want `progress` to consume _chunks_ of `foo` at a time. – sidney Dec 16 '21 at 12:57
  • Are you looking for this, then? https://stackoverflow.com/a/312464/2856376 – DaLynX Dec 16 '21 at 12:58
  • Also no, but thanks. I am already given a `progress` function that progresses an iterator by an _arbitrary_ amount every time. – sidney Dec 16 '21 at 13:00
0

There is no universal way to know in advance if an interator has been exhausted. You basically have to call next() on it and see if it can give you a new value.

while True:
    progress(foo)
except StopIteration:
    pass

Does this do what you want?

Edit: Here is a minimal REPL example of how it works:

>>> it = iter([1,2,3,4,5])
>>> while True:
...   next(it)
...
1
2
3
4
5
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
StopIteration

So when the function progress() tries to get the next value from foo, the exception StopIteration will be raised.

vaizki
  • 1,678
  • 1
  • 9
  • 12
  • Ah, so this doesn't quite work because `progress` won't raise `StopIteration` upon exhaustion of `foo`. – sidney Dec 16 '21 at 13:28
  • To make this a clean and correctly layered I would change `progress()` so that it returns `True` if it got elements with `islice`, and `False` if it got an empty list `[]`. Or return the number of elements it processed. Then you can just do `while progress(foo): pass` – vaizki Dec 16 '21 at 13:38