mapLast, mapFirst, and just general iterator questions

Chris Angelico rosuav at gmail.com
Tue Jun 14 14:47:31 EDT 2022


On Wed, 15 Jun 2022 at 04:07, Travis Griggs <travisgriggs at gmail.com> wrote:
> def mapFirst(stream, transform):
>     try:
>         first = next(stream)
>     except StopIteration:
>         return
>     yield transform(first)
>     yield from stream

Small suggestion: Begin with this:

stream = iter(stream)

That way, you don't need to worry about whether you're given an
iterator or some other iterable (for instance, you can't call next()
on a list, but it would make good sense to be able to use your
function on a list).

(BTW, Python's convention would be to call this "map_first" rather
than "mapFirst". But that's up to you.)

> def mapLast(stream, transform):
>     try:
>         previous = next(stream)
>     except StopIteration:
>         return
>     for item in stream:
>         yield previous
>         previous = item
>     yield transform(previous)

Hmm. This might be a place to use multiple assignment, but what you
have is probably fine too.

> def main():
>     for each in (iterEmpty, iter1, iter2, iterMany):
>         baseIterator = each()
>         chopFirst = mapFirst(baseIterator, lambda x: x[1:-1])
>         andCapLast = mapLast(chopFirst, lambda x: x.upper())
>         print(repr(" ".join(andCapLast)))

Don't bother with a main() function unless you actually need to be
able to use it as a function. Most of the time, it's simplest to just
have the code you want, right there in the file. :) Python isn't C or
Java, and code doesn't have to get wrapped up in functions in order to
exist.

> Is this idiomatic? Especially my implementations of mapFirst and mapList there in the middle? Or is there some way to pull this off that is more elegant?
>

Broadly so. Even with the comments I've made above, I wouldn't say
there's anything particularly *wrong* with your code. There are, of
course, many ways to do things, and what's "best" depends on what your
code is doing, whether it makes sense in context.

> I've been doing more with iterators and stacking them (probably because I've been playing with Elixir elsewhere), I am generally curious what the performance tradeoffs of heavy use of iterators and yield functions in python is. I know the argument for avoiding big list copies when moving between stages. Is it one of those things where there's also some overhead with them, where for small stuff, you'd just be better list-ifying the first iterator and then working with lists (where, for example, I could do the first/last clamp operation with just indexing operations).
>

That's mostly right, but more importantly: Don't worry about
performance. Worry instead about whether the code is expressing your
intent. If that means using a list instead of an iterator, go for it!
If that means using an iterator instead of a list, go for it! Python
won't judge you. :)

But if you really want to know which one is faster, figure out a
reasonable benchmark, and then start playing around with the timeit
module. Just remember, it's very very easy to spend hours trying to
make the benchmark numbers look better, only to discover that it has
negligible impact on your code's actual performance - or, in some
cases, it's *worse* than before (because the benchmark wasn't truly
representative). So if you want to spend some enjoyable time exploring
different options, go for it! And we'd be happy to help out. Just
don't force yourself to write bad code "because it's faster".

ChrisA


More information about the Python-list mailing list