-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Implement "yield from" #366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Original http://legacy.python.org/dev/peps/pep-0380/ had proposed implementation: http://bugs.python.org/issue11682 , which kinda even supported some optimization. But that's not what ended up being in 3.3 & 3.4 mainline, supposedly because that code (optimizations it tried?) had some issues. Implementations from PEP and mainline are pretty different, up to expecting different parameters on the stack. |
uPy currently compiles "yield from", but the byte code is not implemented. Looking at Python's source in ceval.c (yes, cheating, I know!), YIELD_FROM does not look that crazy difficult. |
Many of the optimizations are going to only be relevant in a saturated multi-core environment (work-stealing queues, etc) and might not be of benefit to the lean execution model of typical microcontroller code. Perhaps the general notion of using a LIFO task queue instead of FIFO in order to improve cache hits and memory locality (are those even relevant on these processors?), but aside from that I'm not sure it's worth chasing many of the other optimizations. |
Well, maybe if you wrote a whole Python interpreter from scratch, it's not crazy difficult for you ;-). But I don't understand all details of it (some of exceptions/generator patches were written in intuitive manner - I just tried ideas how it might work, and if testcases work the same as CPy and vm.c doesn't trigger asserts, that's it). So, re: YIELD_FROM. dis.py tells me: "TypeError: don't know how to disassemble generator objects", so, I can't even verify that uPy bytecode matches CPy's. And looking at YIELD_FROM in ceval.c what I see is that it pops a value for .send() from stack, then decrements IP to point to same YIELD_FROM opcode. So, next iteration will expect to pop a value again, but where's this new value is pushed? And don't forget that large deal of "yield from" magic lives in genobject.c, and there're lots of unclear stuff. For example, gen_yf() function (check if a generator currently yields from another gen) is called only for .throw() and .close(), but not for send(). And then close/throw recursively descends thru gen_yf() chain (not even using a loop), and then .send() apparently doesn't even try to walk frames, but bytecode-interpretes thru them, so it's not only messy, it's also awfully non-optimal in CPy. |
Also, genobject.c:gen_send_ex() visibly pushed sent value on gen's value stack, but what we do is just set TOS, w/o push. Don't ask me why - I wrote above how that code was developed - it just works ;-). Actually, thinking about it now, yield'ed value is on TOS, and naturally would need to be popped from it. Then sent value should be pushed on it. So, what we do is pop/push optimization - instead of this sequence, we just take/put value on TOS. Ok, but how that would map to YIELD_FROM? Should we not pop value just the same? Or would we need to give up on pop/push opt altogether? |
Also, intuitively, yield-from chain of generators is not really a chain, it's stack. But even PEP's code treat as a chain and O(n)-walks it (using C recursion again). Instead, "top-level" gen should point to the deepest gen, that one - to its parent, etc. The problem - is such "toplevel" gen really exists? The matter, while gens seemingly form chain/stack, they still remain individually accessible, so one could .send() value in the middle of stack. So, is it the case that gen used in yield from is marked as "running" for the duration of yield from, so attempt to access it by any other means (e.g. .send()) will lead to RuntimeError? Btw, we don't track running status at all, and apparently would need to, at least to assert correctness. |
Ok, I have rough implementation which does basic working. |
I believe I have failry complete implementation now at https://github.com/pfalcon/micropython/commits/pfalcon . As with #368, most cumbersome if not say annoying was implementing .close() . yield from establishes its own adhoc rules for passing upstream and downstream values and for downstream exceptions. However, it piggybacks on standard exception propagation for upstream exceptions. That already breaks consistency and complicates reasoning/implementation (I have a hypothesis now that PEP380's optimizing impl was not accepted because it doesn't account for this asymmetry). But with .close() and GeneratorExit, it again establishes different exception propagation rules, so it all gets pretty messy. (In this regard I still hope that my code is more clear than CPython's). All in all, I still going to read PEP380 dozen more times, looking for lawyer-style tricks (reading PEP380, I immediately remember old subheading of Python's language manual: "for language lawyers" - PEP380 is truly written in that style, full of formalisms and pretty weak on real rationale for implementation choices). |
As of now, yield from is implemented for both generators and other types. |
Great work. |
…ython#366) atmel-samd: Use our own CDC output cache because the internal cache is only used when the memory isn't aligned even if we're going to change the memory immediately after.
"yield from" is important part of modern Python asynchronous concurrency framework which is being established (asyncio module of py3.4 and friends). But as it's pretty new feature, it's not immediately clear how it should be implemented (especially efficiently). This ticket is open to collected pointers and ideas regarding that.
The text was updated successfully, but these errors were encountered: