Difference between chain(*iterable) vs chain.from_iterable(iterable)
The former can only handle unpackable iterables. The latter can handle iterables that cannot be fully unpacked, such as infinite generators.
Consider
>>> from itertools import chain
>>> def inf():
... i=0
... while True:
... i += 1
... yield (i, i)
...
>>> x=inf()
>>> y=chain.from_iterable(x)
>>> z=chain(*x)
<hangs forever>
Furthermore, just the act of unpacking is an eager, up-front-cost activity, so if your iterable has effects you want to evaluate lazily, from_iterable
is your best option.
chain(*foo(5))
unpacks the whole generator, packs it into a tuple and processes it then.
chain.from_iterable(foo(5))
queries the generator created from foo(5)
value for value.
Try foo(1000000)
and watch the memory usage go up and up.
*
unpacks the iterator, meaning it iterates the iterator in order to pass its values to the function. chain.from_iterable
iterates the iterator one by one lazily.