Search code examples
pythongenerator

python - different behavior of print(*generator, <element>)


Question

Please help understand why the two cases act differently although both use a generator (i for i in range(5)).

>>> print(i for i in range(5))
<generator object <genexpr> at 0x7fc409c02900>
>>> print(*(i for i in range(5)))
0 1 2 3 4
>>> print(*(i for i in range(5)), 5)
0 1 2 3 4 5
>>> _r = (i for i in range(5))
>>> print(_r)
<generator object <genexpr> at 0x7fc409c029e0>
>>> print(*_r)
0 1 2 3 4
>>> print(*_r, 5)
5
>>> print(*(_r), 5)
5

Solution

  • When you use the * operator on a generator expression (or any iterator for that matter), it consumes it:

    my_iterator = (i for i in range(3))
    
    second_iterator = iter(list(range(3)))
    
    # splat operator consumes the generator expression
    print(*my_iterator)
    0 1 2
    print(*my_iterator, "Empty!")
    Empty!
    
    # splat operator also consumes the iterator
    print(*second_iterator)
    0 1 2
    print(*second_iterator, "Also empty!")
    Also empty!
    

    You'd need to recreate it to re-use it, which is what you're doing in your first example:

    s = (i for i in range(3))
    print(*s)
    0 1 2
    
    # You create a new generator expression/iterator
    s = (i for i in range(3))
    print(*s)
    0 1 2
    
    # And so it isn't empty
    # same with the shorthand
    print(*(i for i in range(3))
    0 1 2
    

    Note on range

    Since I've used range for this example, it's important to note that range doesn't share this behavior, but an iterator over range does:

    x = range(3)
    
    print(*x)
    0 1 2
    
    print(*x)
    0 1 2
    # the range object isn't consumed and can be re-used
    
    a = iter(x)
    
    print(*a)
    0 1 2
    
    print(*a)
    
    # prints nothing because `a` has been exhausted
    

    More detail can be found in this answer