5

Consider the following example:

def foo(iterator):
    return sum(iterator) / max(iterator)

Is it safe to reuse the same iterator twice?

farsil
  • 955
  • 6
  • 19
  • It was indeed strange that nobody had answered this simple question. Well, next time I'll try to change my search terms. – farsil Feb 16 '17 at 14:46
  • 1
    You're title is confusing iterators and iterables, some iterables can be iterated over many times (like `lists`) – Chris_Rands Feb 16 '17 at 14:46
  • Hmm I see. Should I edit the title despite the question is marked as duplicate? – farsil Feb 16 '17 at 14:48
  • 3
    @Chris_Rands Your comment is confusing "your" and "you're". –  Feb 17 '17 at 13:25

1 Answers1

3

No, it is not safe. Iterators are not sequences. Here's what happens with that foo() function using a generator, which is itself an iterator:

>>> foo(x for x in range(10))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in foo
ValueError: max() arg is an empty sequence

This is because the iterator is already at its end when after sum() finished its job, so max() is getting no additional items from it. In general, it is not possible to reset an iterator so that it can be cycled through again. In order for the foo() function to properly support iterators, it must be rewritten so that the iterator is cycled through only once, for example by saving the items from iterator into a temporary tuple or list:

def foo(iterator):
    iterable = list(iterator)
    return sum(iterable) / max(iterable)

or, if iterator yields a large number of items, by carefully handling it using a for loop:

def foo(iterator):
    # allows iterables like lists or tuples to be passed as arguments
    iterator = iter(iterator)

    try:
        max_ = next(iterator)
        sum_ = max_
    except StopIteration:
        # iterator yields no items, return some default value
        return 0.0

    for element in iterator:
        sum_ += element
        max_ = max(max_, element)

    return sum_ / max_

This will produce the proper result:

>>> foo(x for x in range(10))
5.0
farsil
  • 955
  • 6
  • 19