[Python-ideas] A cute Python implementation of itertools.tee
Tim Peters
tim.peters at gmail.com
Sun Apr 15 12:52:49 EDT 2018
[Antoine Pitrou <solipsis at pitrou.net>]
> This implementation doesn't work with Python 3.7 or 3.8.
> I've tried it here:
> https://gist.github.com/pitrou/b3991f638300edb6d06b5be23a4c66d6
>
> and get:
> Traceback (most recent call last):
> File "mytee.py", line 14, in gen
> mylast = last[1] = last = [next(it), None]
> StopIteration
>
> The above exception was the direct cause of the following exception:
>
> Traceback (most recent call last):
> File "mytee.py", line 47, in <module>
> run(mytee1)
> File "mytee.py", line 36, in run
> lists[i].append(next(iters[i]))
> RuntimeError: generator raised StopIteration
>
> (Yuck!)
Thanks for trying! I wonder whether that will break other code. I
wrote PEP 255, and this part was intentional at the time:
"""
If an unhandled exception-- including, but not limited to,
StopIteration --is raised by, OR PASSES THROUGH [emphasis added], a
generator function, then the exception is passed on to the caller in
the usual way, and subsequent attempts to resume the generator
function raise StopIteration.
"""
I've exploited that a number of times.
> In short, you want the following instead:
>
> try:
> mylast = last[1] = last = [next(it), None]
> except StopIteration:
> return
No, I don't ;-) If I have to catch StopIteration myself now, then I
want the entire "white True:" loop in the "try" block. Setting up
try/except machinery anew on each iteration would add significant
overhead; doing it just once per derived generator wouldn't.
>> def mytee(xs, n):
>> last = [None, None]
>>
>> def gen(it, mylast):
>> nonlocal last
>> while True:
>> mylast = mylast[1]
>> if not mylast:
>> mylast = last[1] = last = [next(it), None]
> That's smart and obscure :-o
> The way it works is that the `last` assignment changes the `last` value
> seen by all derived generators, while the `last[1]` assignment updates
> the bindings made in the other generators' `mylast` lists... It's
> difficult to find the words to explain it.
Which is why I didn't even try - I did warn people that if they
thought it "was obvious", they hadn't yet thought hard enough ;-)
Good job!
> The chained assignment makes it more difficult to parse as well (when I
> read this I don't know if `last[i]` or `last` gets assigned first;
> apparently the answer is `last[i]`, otherwise the recipe wouldn't work
> correctly).
Ya, I had to look it up too :-) Although, like almost everything else
in Python, chained assignments proceed "left to right". I was just
trying to make it as short as possible, to increase the "huh - can
something that tiny really work?!" healthy skepticism factor :-)
> Perhaps like this:
>
> while True:
> mylast = mylast[1]
> if not mylast:
> try:
> # Create new list link
> mylast = [next(it), None]
> except StopIteration:
> return
> else:
> # Append to other generators `mylast` linked lists
> last[1] = mylast
> # Update shared list link
> last = last[1]
> yield mylast[0]
I certainly agree that's easier to follow. But that wasn't really the point ;-)
More information about the Python-ideas
mailing list