
On Thu, Dec 9, 2021 at 5:52 PM Brendan Barnwell <brenbarn@brenbarn.net> wrote:
On 2021-12-08 20:55, Chris Angelico wrote:
(H) This is true. But if the two syntaxes can be sufficiently similar, the cost should be low, and the feature benefit would be high. Early binding lets you "def f(x=x):" in a loop and capture each x as it goes by. Late binding lets you "def f(x=>[]):" and get a new list every time. Both have their places.
(The "two syntaxes" here is referring to syntaxes for early and late binding.) I'm actually worried about the reverse. When the two syntaxes are similar, it will be easier to mistake one for the other.
For the most part, there won't be any confusion. If the default is a simple value, it's going to be early-bound (eg "def list.pop(index=-1):"). If it's a mutable that needs to be constructed, it'll be late-bound. The distinction will be important if it's looked up from another namespace ("def fetch(timeout=some.default.timeout):"), but even then, the distinction is whether the default is affected by future changes or not; the two forms are functionally very similar.
x = 1/y if y else "invalid"
There's no object for "1/y". Trying to create one would be a nightmare of subtleties, where assignment expressions would break things, nonlocal variable references would become tricky, etc, etc. Similarly:
As I have stated repeatedly, ternary expressions are not parallel because they do not have a distinct definition-time and call-time. Please stop bringing up the ternary operator case and pretending it is the same as a function. It is not, for reasons that I have already explained several times.
To try stating this in yet another way, currently if I have:
def f(a=<some code here>)
<some code here> must be something that evaluates to a first-class object, and the "argument default" IS that first-class object --- not bytecode to generate it, not some behavior that evaluates the expression, no, the default value is itself an object. This would not be the case for late-bound defaults under the PEP. (Rather, as I phrased it in another post, there would not "be" a late-bound default at all; there would just be some behavior in the function to do some stuff when that argument isn't passed.)
The VALUE is a first-class object - that's the result of evaluating the expression. With early-bound defaults, that's the only thing that gets saved - not the expression, just the resulting value. (Which can be seen if you do something like "def f(x=0x100):", which will show the default as 256.) Remember, a late-bound default is most similar to this code: def f(a=<optional>): if a was omitted: a = <some code here> And in that form, the code isn't available as a first-class object. That's why I say that it is parallel to every other partial expression in Python. Until you evaluate it, there is no first-class object representing it. (A code object comes close, but it's more than just an expression - it also depends on its context. A function requires even more context.) Suppose you wanted, for a function with an early-bound default, to see the actual expression that got you there. This can be synthesized in some cases (eg an enum with a good repr), but in the general case, how can you figure out that expression? Could you reevaluate it? Suppose you wanted to make a decorator like this: @reevaluate_defaults_every_call def f(x=[], y=func(), z=x.length + y): ... Could you, using early-bound defaults, get hold of the expressions? Are they first-class objects? ChrisA