On Sat, Dec 4, 2021 at 4:48 AM Eric V. Smith <eric@trueblade.com> wrote:
On 12/2/2021 6:36 PM, Chris Angelico wrote:
On Fri, Dec 3, 2021 at 7:54 AM Eric V. Smith <eric@trueblade.com> wrote:
Say I have a function with an early-bound default. I can inspect it and I can change it. One reason to inspect it is so that I can call the function with its default values. This is a form of wrapping the function. I realize "just don't pass that argument when you call the function" will be the response, but I think in good faith you'd have to admit this is more difficult than just passing some default value to a function call.
1) I want to call this function 2) I may want to not pass this argument 3) Ah, perfect! I will pass this argument with a value of somemod._SENTINEL.
Or alternatively:
1) I want to call this function. 2) Prepare a dictionary of arguments. Leave out what I don't want. 3) If I want to pass this argument, add it to the dictionary.
This way doesn't require reaching into the function's private information to use a sentinel. Yes, it may be a tad more difficult (though not VERY much), but you're also avoiding binding yourself to what might be an implementation detail.
Your version is less friendly to type checking. And it doesn't work with positional-only arguments.
Positional-only args can be done with a list instead of a dict. Not much harder, although less clear what's happening if you have multiple.
How is the sentinel value private information or an implementation detail? It's part of the API. It should be clearly documented. If nothing else, it's can be inspected and discovered.
Depends on the sentinel. In this example, is the exact value of the sentinel part of the API? _SENTINEL = object() def frobnicate(stuff, extra=_SENTINEL): ... If the sentinel is None, then it may be part of the API. (Of course, if it's a deliberately-chosen string or something, then that's completely different, and then you're not really doing the "optional argument" thing that I'm talking about here, and there's a very real default value.) But when it's an arbitrary sentinel like this, I argue that the precise value is NOT part of the function's API, only that you can pass any object, or not pass an object at all.
As far as changing the defaults, consider:
def f(x=3): return x ... f() 3 f.__defaults__=(42,) f() 42
The current PEP design does not provide for this functionality for late-bound defaults. Remember, though: the true comparison should be something like this:
_SENTINEL = object() def f(x=_SENTINEL): if x is _SENTINEL: x = [] return x
Can you change that from a new empty list to something else? No. All you can do, by mutating the function's dunders, is change the sentinel, which is actually irrelevant to the function's true behaviour. You cannot change the true default. It is none the less true that default late-bound values cannot be modified. Correct? Early-bound ones can.
Yes, but I'm asking you to compare late-bound defaults with the "sentinel and replace it in the function" idiom, which is a closer parallel. Can you, externally to the function, change this to use a new empty set instead of a new empty list? No.
I realize the response will be that code shouldn't need to do these things, but I do not think we should be adding features to python that limit what introspections and runtime modifications user code can do. The response is more that the code CAN'T do these things, by definition. To the extent that you already can, you still can. To the extent that you should be able to, you are still able to. (And more. There are things you're capable of with PEP 671 that you definitely shouldn't do in normal code.)
This is a tautology. You can't do these things if 671 is accepted because they will defined as not doable by 671. That's a strike against it.
My point is that you *already* cannot do them. My proposal doesn't stop you from doing things you currently can do, it just makes it easier to spell the parts you can do.
My stance is that it should be possible, and a proposal that makes them not possible with late-bound arguments is deficient.
Please revisit your concerns with regard to the "sentinel and replace in the function" idiom, as I've stated in the past few posts. The sentinel itself can be replaced, but that is useless if the function's body is still looking for the old sentinel.
We're going to have to disagree about this. I think it's critical that Signature objects be usable with all types of defaults. And having the default value available as a string isn't very useful, except for displaying help text. It wouldn't be possible to create a new Signature object from an existing Signature object that contains a late-bound argument (say, to create a new Signature with an additional argument). At least I haven't seen how it would be possible, since the PEP makes no mention of Signature objects. Which it definitely should, even if only to say "late-bound arguments are not designed to work with Signature objects".
They most certainly ARE usable with all types of defaults, but instead of a meaningless "=<object object at 0xasdfqwer>", you get "=[]".
Sentinels are not meaningless. I think you're alienating people every time you suggest they are.
Some of them are. Some of them are not. When they are meaningful, PEP 671 does not apply, because they're not the sort of sentinel I'm talking about. Unfortunately, the word "sentinel" means many different things. There are many cases where a sentinel is a truly meaningful and useful value, and in those cases, don't change anything. There are other cases where the sentinel is a technical workaround for the fact that Python currently cannot represent defaults that aren't constant values, and those are meaningless sentinels that can be removed.
In any event, I've expended enough volunteer time discussing this for now, so I'm going to drop off. I'm against another way to specify function argument defaults, and I'm against some of the decisions that PEP 671 has made. I've stated those objections. I'll argue against it further when the SC is asked to consider it.
No problem. I don't expect everyone to agree with me, and if it ever happened, I would wonder why :) ChrisA