In the more_itertools library, we have the function windowed, which has this behavior: >>> from more_itertools import windowed >>> xs = [1, 2, 3, 4] >>> list(windowed(xs, 3)) [(1, 2, 3), (2, 3, 4)] >>> list(windowed(xs, 2)) [(1, 2), (2, 3), (3, 4)] >>> list(windowed(xs, 1)) [(1,), (2,), (3,), (4,)] >>> list(windowed(xs, 0)) [()] I can understand the argument for windowed(xs, 0) being an empty range (consistent with zip() being empty) and there is a separate argument for windowed(xs, 0) being an infinite range (which would be consistent with zip() being an infinite range, which would be the mathematically correct identity element — Julia does this, for instance). But why does windowed(xs, 0) specifically yield a range consisting of one empty tuple? In the source code, this is handled as a special case, it's not like it falls out from the implementation. What is the motivation for specifically just yield() here: def windowed(seq, n, fillvalue=None, step=1): # ... snipping docstring ... if n < 0: raise ValueError('n must be >= 0') if n == 0: yield () return if step < 1: raise ValueError('step must be >= 1') # ... rest of implementation ... Continue reading...