On 2021-12-02 00:31, Chris Angelico wrote:
Here's how a ternary if looks:
>>>def f(n):
... return 0 if n == 0 else 42/n
...
>>>dis.dis(f)
2 0 LOAD_FAST 0 (n)
2 LOAD_CONST 1 (0)
4 COMPARE_OP 2 (==)
6 POP_JUMP_IF_FALSE 6 (to 12)
8 LOAD_CONST 1 (0)
10 RETURN_VALUE
>> 12 LOAD_CONST 2 (42)
14 LOAD_FAST 0 (n)
16 BINARY_TRUE_DIVIDE
18 RETURN_VALUE
The "42/n" part is stored in f.__code__.co_code as the part that says
"LOAD_CONST 42, LOAD_FAST n, BINARY_TRUE_DIVIDE". It's not an object.
It's just code - three instructions.
Here's how (in the reference implementation - everything is subject to
change) a late-bound default looks:
>>>def f(x=>[]): print(x)
...
>>>dis.dis(f)
1 0 QUERY_FAST 0 (x)
2 POP_JUMP_IF_TRUE 4 (to 8)
4 BUILD_LIST 0
6 STORE_FAST 0 (x)
>> 8 LOAD_GLOBAL 0 (print)
10 LOAD_FAST 0 (x)
12 CALL_FUNCTION 1
14 POP_TOP
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
The "=>[]" part is stored in f.__code__.co_code as the part that says
"QUERY_FAST x, and if false, BUILD_LIST, STORE_FAST x". It's not an
object. It's four instructions in the bytecode.
In both cases, no part of the expression is ever re-executed. I'm not
understanding the distinction here. Can you explain further please?
Your explanation exactly shows how it IS re-executed. I'm not totally
clear on this disassembly since this is new behavior, but if I
understand right, BUILD_LIST is re-executing the expression `[]` and
STORE_FAST is re-assigning it to x. The expression `[]` is
syntactically present in the function definition but its execution has
been shoved into the function body where it may be re-executed many
times (any time the function is called without passing a value).
What do you mean when you say it is not re-executed? Is it not the
case that `[]` is syntactically present in the `def` line (which is
executed only once, and whose bytecode is not shown) yet its
implementation (BUILD_LIST) is in the function body, which may be
executed many times? How is the BUILD_LIST opcode there not being
re-executed on later calls of the function?
Perhaps what you are saying is that what is stored is not the literal
string "[]" but bytecode that implements it? That distinction is
meaningless to me. The point is that you wrote `[]` once, in a line
that is executed once (the function definition itself), but the `[]` is
executed many times, separately from the function definition.
Another way to put it is, again, the examples are not parallel. In
your first example the ternary expression is (syntactically) part of the
function BODY, so of course it appears in the disassembly. In your
second example, the late-bound default is not in the body, it is in the
signature. The disassembly is only showing the bytecode of the function
body, but the late-bound default is syntactically enmeshed with the
function DEFINITION. So I don't want the late-bound default code to be
re-executed unless the function is re-defined. Or, if you're going to
store that `x = []`, I don't want it to be "stored" by just shoving it
in the function body, I want it to be some kind of separate object.
Here's an example that may make my point clearer.
some_function(x+1, lambda x: x+2)
This is our best current approximation to some kind of "late
evaluation". The first argument, `x+1`, is evaluated before the
function is called, and the function gets only the result. The second
argument is also of course evaluated before the function is called, but
what is evaluated is a lambda; the `x+2` is not evaluated. But the idea
of "evaluate x+2 later" is encapsulated in a function object. Without
knowing anything about `some_function`, I still know that there is no
way it can ever evaluate `x+2` without going through the interface of
that function object. There is no way to pass, as the second argument
to `some_function` some kind of amorphous "directive" that says
"evaluate x+2 later" without wrapping that directive up into some kind
of Python object.
Perhaps the fundamental point that I feel you're missing about my
position is that a ternary expression does not have a "definition" and a
"body" that are executed at separate times. There is just the whole
ternary expression and it is evaluated all at once. Thus there can be
no parallel with functions, which do have a separation between
definition time and call time. Indeed, it's because these are separate
that late vs. early binding of defaults is even a meaningful concept for
functions (but not for ternary expressions).
So what I am saying is if I see a line like this:
def f(a=x+1, b@=x+2):
The x+2 is syntactically embedded within the `def` line, that is, the
function definition (not the body). Thus there are only two kinds of
semantics that are going to make me happy:
1) That `x+2`(or any bytecode derived from it, etc.) will never be
re-executed unless the program execution again reaches the line with the
`def f` (which is what we have with early-bound defaults)
2) A Python object is created that encapsulates the expression `x+2`
somehow and defines what it means to "evaluate it in a context" (e.g.,
by referencing local variables in the scope where it is evaluated)
Maybe another way to say this is just "I want any kind of late-bound
default to really be an early-bound default whose value is some object
that provides a way to evaluate it later". (I'm trying to think of
different ways to say this because it seems what I'm saying is not clear
to you. :-)
--
Brendan Barnwell
"Do not follow where the path may lead. Go, instead, where there is no
path, and leave a trail."
--author unknown
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/python-ideas@python.org/message/IQ7C4MXF25KJ7HXAMTPLOBFQK2N42WU2/
Code of Conduct: http://python.org/psf/codeofconduct/