On Fri, Dec 3, 2021 at 2:30 PM Brendan Barnwell <brenb...@brenbarn.net> wrote:
>
> On 2021-12-02 15:40, Chris Angelico wrote:
> > Actually, no. I want to put the default arguments into the signature,
> > and the body in the body. The distinction currently has a technical
> > restriction that means that, in certain circumstances, what belongs in
> > the signature has to be hacked into the body. I'm trying to make it so
> > that those can be put where they belong.
>
>         Chris, I know this is probably not your intention, but I feel the
> discussion is continually muddle by you just saying "default arguments"
> as if everyone agrees on what those are and the issue is just where to
> put them.  But clearly that is not the case.  You seem to take it for
> granted that "len(x) evaluated when the function is called" "is" a
> "default argument" in the same sense that an early-bound default like
> the number 3 is.  I do not agree, and it's pretty clear David Mertz does
> not agree, and I think there are others here who also do not agree.
>
>         It's not as is there is some pre-existing notion of "default argument"
> in Python and you are just proposing to add an implementation of it that
> was left out due to some kind of oversight.  Your proposal is CHANGING
> the idea of what a default argument is.  I get that it's natural to
> refer to your new proposal as "default arguments" but I've now seen a
> number of messages here where you say "but no we should do X because
> this is a default argument", taking for granted that what you're talking
> about is already agreed to be a default argument.  (No doubt I and many
> others are also guilty of similar missteps by saying "default argument"
> as a shorthand for something or other, and I'll try to be careful about
> it myself.)

Some functions most assuredly DO have a pre-existing notion of
"default argument". Some do not. Allow me to give a few examples:

def getattr(object, name, default):

Omitting the third argument does not have a default. It will cause
different behaviour (getattr will raise an exception).

def dict.get(key, default=None):

Omitting the last argument behaves exactly as if you passed None. This
has a default; the function behaves identically whether you pass an
argument or not, and you can determine what the behaviour would be if
you do. (These two are a little confusing in that they use the name
"default", but the fact is that default arguments are often used for
defaults, surprise surprise. So I'm going to be reusing that word a
lot.)

def open(file, mode='r', encoding=????):

Omitting mode is exactly the same as passing 'r'. Omitting encoding is
exactly the same as passing locale.getpreferredencoding(False). Both
of these have real defaults.

def bisect.bisect(a, x, lo=0, hi=len(a)):

Omitting lo is exactly the same as passing 0. Omitting hi is exactly
the same as passing len(a). Both of these have real defaults.

For parameters that do not have defaults, PEP 671 has nothing to say.
Continue doing what you're already doing (whether that's a sentinel,
or *args, or whatever), as there's nothing needing to be changed.

For parameters whose defaults are simple constants, PEP 671 also has
nothing to say. Continue defining them as early-bound defaults, and
the behaviour will not change.

The difference comes with those arguments whose true default is
calculated in some way, or is dynamic. These really truly do have
default values, and there is absolutely no behavioural difference
between hi=len(a) and omitting the hi parameter to bisect(). Due to a
technical limitation, though, the documentation for bisect() has to
explain this in the docstring rather than the function signature.

Would this code pass review?

def spaminate(msg, times=None):
    if times is None: times = 50
    ...

Unless there's a very VERY good reason for using None as the default,
shouldn't it use 50 in the signature? It would be a clearer
declaration of intent: omitting this argument is the same as passing
50.

>         By my definition as of now, a default argument has to be an object.
> Thus what your proposal envisions are not default arguments.  You can't
> just say "I want to do this with default arguments".  You need to
> provide an argument for why we should even consider these to be default
> arguments at all.

Yes, that is a current, and technical, limitation. Consider this:

https://en.wikipedia.org/wiki/Default_argument#Evaluation

Python is *notable* for always evaluating default arguments just once.
The concept of default args, as described there and as understood by
anyone that I have asked (mainly students, which I admit isn't an
unbiased sample, but nothing is), is that they should be evaluated at
call time.

My argument for why they should be considered default arguments is
that, aside from Python being unable to represent them, they *are*
precisely what default arguments are.

Suppose you're on the ECMAScript committee, and someone proposes that
the language support bignums. (That's not truly hypothetical,
incidentally - I saw references to such a proposal.) Would you argue
that 9007199254740993 is not really a number, on the basis that
current ECMAScript cannot represent it? Would you force someone to
prove that it is really a number? It's only special because of
implementation restrictions.

>         Perhaps a way of stating this without reference to arguments is this:
> you want to put code in the function signature but not have it executed
> until the function is called.  I do not agree with that choice.  The
> function signature and the function body are different syntactic
> environments with different semantics.  Everything in the function
> signature should be executed when the function is defined (although of
> course that execution can result in an object which contains deferred
> behavior itself, like if we pass a function as an argument to another).

That's reasonable, but I disagree from a perspective of practicality:
logically and usefully, it is extremely helpful to be able to describe
arguments that way. Passing positional parameters is approximately
equivalent to:

def func(*args):
    """func(a, b, c=1, d=a+b)"""
    a, args = args
    b, args = args
    if args: c, args = args
    else: c = 1
    if args: d, args = args
    else: d = a + b

If you try to explain it to someone who's learning about default
argument values, do you first have to explain that they get evaluated
and recorded somewhere, and these indescribable values are what's
actually assigned? Or would you describe it mostly like this (or as
"if c is omitted: c = 1", which comes to the same thing)?

Argument default snapshotting is an incredibly helpful performance
advantage, but it isn't inherent to the very concept of default
arguments.

>   Only the function body should be executed when the function is called.
>   If we want to provide some way to augment the function body to assign
> values to missing arguments, I wouldn't rule that out completely, but in
> my view the function signature is not an available space for that.  The
> function signature is ONLY for things that happen when the function is
> defined.  It is too confusing to have the function signature mix
> definition-time and call-time behavior.

The trouble is that the function signature MUST be the one and only
place where you figure out whether the argument is optional or
mandatory. Otherwise there is an endless sea of debugging nightmares -
just ask anyone who works regularly in JavaScript, where all arguments
are optional and will default to the special value 'undefined' if
omitted.

So you'd need to separate "this is optional" from "if omitted, do
this". That might be a possibility, but it would need two separate
compiler features:

def func(a, b, c=1, d=?):
    if unset d: d = a+b

where "unset N" is a unary operator which returns True if the name
would raise, False if not. I'm not proposing that, but if someone
wants to, I would be happy to share my reference implementation, since
90% of the code is actually there :)

ChrisA
_______________________________________________
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/KZCA6X4XSSD4IWL4DNXZGCX5XQSR522I/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to