I find this refactored code intriguing. While I would not suggest changes to the itertools recipes, it is a pleasant exercise to think of alternative recipes with an itertools way.
There are a few third-party libraries dedicated to itertools recipes. For example, more-itertools has an iter-like recipe called `more_itertools.interleave_longest`. It turns out it behaves like `round_robin` and the implementation happens to be similar to your suggestion: _marker = object() def interleave_longest(*iterables): """Return a new iterable yielding from each iterable in turn, skipping any that are exhausted. >>> list(interleave_longest([1, 2, 3], [4, 5], [6, 7, 8])) [1, 4, 6, 2, 5, 7, 3, 8] """ i = chain.from_iterable(zip_longest(*iterables, fillvalue=_marker)) return filter(lambda x: x is not _marker, i) For comparison, your suggestion: def roundrobin(*iters): > "roundrobin('ABC', 'D', 'EF') --> A D E B F C" > # Perhaps "flat_zip_nofill" is a better name, or something similar > sentinel = object() > for tup in it.zip_longest(*iters, fillvalue=sentinel): > yield from (x for x in tup if x is not sentinel) > In summary, both versions zip iterables and filter sentinels. I believe `yield from` in your suggestion restricts your recipe to Python 3 (which can be resolved with a for loop). Aside, given some performance gain, such third-party libraries may be better suited for your particular contribution. On Thu, Nov 16, 2017 at 8:56 AM, bunslow <buns...@gmail.com> wrote: > For taking values alternately from a series of iterables, there's two > primary functions: > > builtin.zip > itertools.zip_longest > > zip of course stops when the shortest iterable ends. zip_longest is > generally a useful substitute for when you don't want the zip behavior, but > it fills extra values in the blanks rather than just ignoring a finished > iterator and moving on with the rest. > > This latter most use case is at least somewhat common, according to > this[1] StackOverflow question (and other duplicates), in addition to the > existence of the `roundrobin` recipe[2] in the itertools docs. The recipe > satisfies this use case, and its code is repeated in the StackOverflow > answer. > > However, it is remarkably unpythonic, in my opinion, which is one thing > when such is necessary to achieve a goal, but for this functionality, such > is most definitely *not* necessary. I'll paste the code here for quick > reference: > > > def roundrobin(*iterables): > "roundrobin('ABC', 'D', 'EF') --> A D E B F C" > pending = len(iterables) > nexts = cycle(iter(it).__next__ for it in iterables) > while pending: > try: > for next in nexts: > yield next() > except StopIteration: > pending -= 1 > nexts = cycle(islice(nexts, pending)) > > > Things that strike me as unpythonic: 1) requiring the total number of > input iterables 2) making gratuitous use of `next`, 3) using a while loop > in code dealing with iterables, 4) combining loops, exceptions, and > composed itertools functions in non-obvious ways that make control flow > difficult to determine > > Now, I get it, looking at the "roughly equivalent to" code for zip_longest > in the docs, there doesn't seem to be much way around it for generally > similar goals, and as I said above, unpythonic is fine when necessary > (practicality beats purity), but in this case, for being a "recipe" in the > itertools docs, it should *make use* of the zip_longest which already does > all the unpythonic stuff for you (though honestly I'm not convinced either > that the zip_longest code in the docs is the most possible pythonic-ness). > Instead, the following recipe (which I also submitted to the StackOverflow > question, and which is generally similar to several other later answers, > all remarking that they believe it's more pythonic) is much cleaner and > more suited to demonstrating the power of itertools to new developers than > the mess of a "recipe" pasted above. > > > def roundrobin(*iters): > "roundrobin('ABC', 'D', 'EF') --> A D E B F C" > # Perhaps "flat_zip_nofill" is a better name, or something similar > sentinel = object() > for tup in it.zip_longest(*iters, fillvalue=sentinel): > yield from (x for x in tup if x is not sentinel) > > > In particular, this is just an extremely thin wrapper around zip_longest, > whose primary purpose is to eliminate the otherwise-mandatory "fillvalues" > that zip_longest requires to produce uniform-length tuples. It's also an > excellent example of how to make best pythonic use of iterables in general, > and itertools in particular, and as such a much better implementation to be > demonstrated in documentation. > > I would thus advocate that the former recipe is replaced with the latter > recipe, being much more pythonic, understandable, and useful for helping > new developers acquire the style of python. (Using the common linguistics > analogy: a dictionary and grammar for a spoken language may be enough to > communicate, but we rely on a large body of literature -- fiction, > research, poetry, etc -- as children to get that special flavor and most > expressive taste to the language. The stdlib is no Shakespeare, but it and > its docs still form an important part of the formative literature of the > Python language.) > > I realize at the end of the day this is a pretty trivial and ultimately > meaningless nit to pick, but I've never contributed before and have a > variety of similar minor pain points in the docs/stdlib, and I'm trying to > gauge 1) how well this sort of minor QoL improvement is wanted, and 2) even > if it is wanted, am I going about it the right way. If the answers to both > of these questions are positive regarding this particular case, then I'll > look into making a BPO issue and pull request on GitHub, which IIUC is the > standard path for contributions. > > Thank you for your consideration. > > ~~~~ > > [1]: https://stackoverflow.com/questions/3678869/pythonic- > way-to-combine-two-lists-in-an-alternating-fashion/ > > [2]: https://docs.python.org/3/library/itertools.html#itertools-recipes > > _______________________________________________ > Python-ideas mailing list > Python-ideas@python.org > https://mail.python.org/mailman/listinfo/python-ideas > Code of Conduct: http://python.org/psf/codeofconduct/ > >
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/