A long time ago, I wrote some stuff on how generators are not lists (okay, technically it was about iterators), and one of the things that I mentioned is that generators do not have list methods. Well, there's a consequence of that that only struck me recently: you need completely different code to modify a returned generator than to modify a returned list .
Suppose you have a function that returns something that is conceptually a list of items. Further suppose that you have another function that modifies what the first function returns; perhaps you want to add something on the end. If you know you're dealing with a list, you write:
def append(func, extra):
r = func()
r.extend(extra)
return r
If
func()
is a generator, this code blows up. You have two choices; first, you can forcefully turn the result of
func()
into a list, and second, you can rewrite
append()
as a generator (which will work regardless of what
func()
returns, but may have consequences that make it undesirable):
def append(func, extra):
for it in (func(), extra):
for e in it:
yield e
(Yes, yes, one can write this using
itertools.chain()
. Then people would have to look it up .)
In either case, you have to actively make a decision about what your function will do. You cannot passively modify whatever you get handed and pass it up to your caller without changing its nature; you must decide that no matter what
func()
returns, you're either returning a list or an iterator.
(Technically you can, since you can see if you got handed something that follows the iterator protocol or whether it looks sequence-like. But that way lies madness.)