-
-
Notifications
You must be signed in to change notification settings - Fork 29.2k
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
Generator return value ignored in lambda function #67381
Comments
As yield is an expression, it's legal in a lambda function, which then $ python3
Python 3.5.0a0 (default:1c51f1650c42+, Dec 29 2014, 02:29:06)
[GCC 4.7.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> f=lambda: (yield 5)
>>> x=f()
>>> next(x)
5
>>> x.send(123)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> def f(): return (yield 5)
...
>>> x=f()
>>> next(x)
5
>>> x.send(123)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: 123
>>> x = (lambda: print((yield 1)) or 2)()
>>> next(x)
1
>>> x.send(3)
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration The last example demonstrates that send() is working, but the return value is not getting propagated. Disassembly shows this: >>> dis.dis(lambda: (yield 5))
1 0 LOAD_CONST 1 (5)
3 YIELD_VALUE
4 POP_TOP
5 LOAD_CONST 0 (None)
8 RETURN_VALUE
>>> def f(): return (yield 5)
...
>>> dis.dis(f)
1 0 LOAD_CONST 1 (5)
3 YIELD_VALUE
4 RETURN_VALUE I'm sure this is a bug that will affect very approximately zero people, but it's still a peculiar inconsistency! Verified with 3.5 and 3.4. |
Hm, looks like nobody bothered to update the lambda code generation to use the value from yield. I almost feel like there is some unnecessary check "if we are in a lambda" in the code generation for yield. Have you looked through the code generation yet? |
I'm not sure what to look for in the code generation. In compile.c lines 3456 and following, there's a LOAD_CONST None coming through, in the else branch of "if (e->v.Yield.value)", but nothing talking about lambda functions. There are constants COMPILER_SCOPE_LAMBDA and COMPILER_SCOPE_FUNCTION, but the only place where they're used is compiler_set_qualname() and I can't see anything obvious there. Hopefully someone more familiar with the code internals will be able to figure this out! |
Here are the operations being emitted (line, macro used and eventual argument): >>> f = lambda: (yield 5)
3487: ADDOP_O LOAD_CONST e->v.Num.n
3472: ADDOP YIELD_VALUE
1907: ADDOP_IN_SCOPE POP_TOP
4349: ADDOP_O LOAD_CONST Py_None
4350: ADDOP RETURN_VALUE
1457: ADDOP_O LOAD_CONST (PyObject*)co
1458: ADDOP_O LOAD_CONST qualname
1459: ADDOP_I MAKE_FUNCTION args
4349: ADDOP_O LOAD_CONST Py_None
4350: ADDOP RETURN_VALUE
>>> def g(): return (yield 5)
...
3487: ADDOP_O LOAD_CONST e->v.Num.n
3472: ADDOP YIELD_VALUE
2533: ADDOP RETURN_VALUE
1457: ADDOP_O LOAD_CONST (PyObject*)co
1458: ADDOP_O LOAD_CONST qualname
1459: ADDOP_I MAKE_FUNCTION args
4349: ADDOP_O LOAD_CONST Py_None
4350: ADDOP RETURN_VALUE So there's an extra POP_TOP + LOAD_CONST Py_NONE for the lambda version that throws away the "123" (in the exemple). The attached patch (0001-...) fixes it. However please note that I'm not knowledgable about that part of the code and devised the patch empirically. |
Could you please add a test based on Chris's example? And it would be good to add a test for a lambda with "yield from". |
Here is a working test, testing yield by lambda & function as well as lambda and function yielding from those. |
New changeset 2b4a04c3681b by Serhiy Storchaka in branch '3.4': New changeset a3b889e9d3f3 by Serhiy Storchaka in branch 'default': |
Committed with non-dis test. Thank you for your contribution Bruno. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: