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/

Reply via email to