"You've gone from talking about *my* intentions, which you got wrong, to
an argument about which is "more explicit"."

No. You were the one who brought up explicitness by claiming that using a sentinel value was a way to "explicitly tell the function "give me the default value" (whatever the heck that means). Now that I've deconstructed that argument, you're moving the goal post from "my way is more explicit" to "there is such a thing as *too* explicit, don't ya know?!" and blaming me for shifting the focus to "which is more explicit"... cool.

"In the simple example of the default being a new empty list:

    # Using Chris' preferred syntax, not mine
    def func(arg=>[]):

then I agree"

Which is, what? 99% of the use cases? great!

"But let's talk about more complex examples:

    def open(filename, mode='r', buffering=-1): ...

How would you make that "more explicit"?

    def open(filename, mode='r', buffering=>(
            1 if 'b' not in mode
                 and os.fdopen(os.open(filename)).isatty()
            else _guess_system_blocksize() or io.DEFAULT_BUFFER_SIZE
            )
        ): ...

Too much information! We're now drowning in explicitness."

Then just use the sentinel hack, or write a helper function that has a nice name that says what the default will be and use that. I'm positive there will always be corner cases where logic that's concerned with checking and setting the right values for parameters before the actual business logic starts.

"Now how do I, the caller, *explicitly* indicate that I want to use that
system-dependent value? I just want a nice, simple value I can
explicitly pass as buffering to say "just use the default".

    with open('myfile.txt', buffering= what? ) as f:

I can't. So in this case, your agonisingly explicit default expression
forces the caller to be *less* explicit when they call the function."

This seems like an exceedingly minor complaint. The obvious answer is: you invoke the default behaviour by not passing the argument. And yes, I know that's less explicit, but that's kinda the whole point of defaults to begin with. It's behavior when the user decides not to specify their own. Nobody's asking "what do I pass to logging.config.fileConfig to explicitly invoke the logging module's default behavior?!" The answer is simply: don't configure logging. It's not rocket science.

This want to explicitly pass something to invoke default behavior brings this old Raymond Hetinger talk to mind where he suggests that sometimes passing positional arguments by name can help make your code more readable. I wholeheartedly agree with the practice discussed in Hettinger's talk, but I think your example is completely different.

When you see `twitter_search('@obama', False, 20, True)` that naturally invokes the question "what the heck are those arguments??" 
When you see `with open('myfile.txt') as f` it doesn't raise the question "what is the buffering argument?"

I don't know if that's why you think the ability to explicitly invoke default behavior is so important, but I thought I'd address it anyway. 

"Can we agree that, like salt, sugar, and bike-shedding, sometimes you
can have *too much* explicitness in code?"

Surely *you* would never agree that there's such a thing as *too much* bike-shedding! Isn't that your raison d'être?!

I never said one had to be a Nazi about stamping out every use of the sentinel... er... idiom (if you prefer it to hack). Sure, use it in those rare corner cases where expressing a late-bound default would be so very burdensome. The fact that there are such cases isn't a very good argument against this proposal. It's more like "HA! You can't fix *everything* can you?!". Great. You got me, Steve. *slow claps*

"The caller's perspective is important. The caller has to read the
function signature (either in the documentation, or if they use
`help(function)` in the interpreter, or when their IDE offers up
tooltips or argument completion). The caller has to actually use the
function. We should consider their perspective."

I never said we shouldn't. That's just not what I was talking about at that time and you were misinterpreting my argument. 

"Nah, I'm kidding of course. But the point I am making is that you are
mocking the concept by using a totally silly example. "monkey_pants"
indeed, ha ha how very droll. But if you used a more meaningful symbol,
like "default", or some enumeration with a meaningful name, or a value
which is a standard idiom such as -1, then the idea isn't so ridiculous.

And None is one such standard idiom."

You missed my point entirely. The point is that the reasons you were giving for sentinels both being explicit and not being a hack don't hold water.
Namely: The fact that it's guaranteed to work doesn't make it not a hack, and your inexplicable reasoning that defaulting to a sentinel instead of the actual default value that parameter is supposed to take after all is said and done is somehow more explicit than defaulting to the default value in the first place.

Just because it's an idiom, doesn't mean it's actually good. I think the `if __name__ == "__main__":` idiom is a horrible, round-about way of saying "if this file is being run as a script". You might look at that and say "What do you mean? That's exactly what it says!" because you've seen the idiom so many times, just like how you think "x=None" explicitly says "give x the default value", but if you look closely `if __name__ == "__main__":` *doesn't* actually say anything like "if this file is being run as a script", it says a bunch of esoteric and confusing garbage that you've learned to read as "if this file is being run as a script".

"> Steven D'Aprano
> "How is it misleading?"
>
> Your way of having a parameter default to an empty list (or whatever) is by
> having the parameter default to None. That's how its misleading.

All you have done is repeat that it is misleading. I'm still no clearer
why you think it is misleading" 

Seriously? If I said "I'm going to pat my head" then proceeded to rub my tummy, would you not think that's a bit misleading?
Again, if you look closely, I *didn't* simply repeat that it's misleading. I pointed out the contradiction between intent and code. 


On Thu, Dec 2, 2021 at 8:57 PM Steven D'Aprano <steve@pearwood.info> wrote:
On Thu, Dec 02, 2021 at 01:20:27PM -0800, abed...@gmail.com wrote:

> If you want the parameter to default to an empty list, it's categorically
> more explicit to have the parameter default to an empty list.

You've gone from talking about *my* intentions, which you got wrong, to
an argument about which is "more explicit".

In the simple example of the default being a new empty list:

    # Using Chris' preferred syntax, not mine
    def func(arg=>[]):

then I agree: once you know what the syntax means, then it is more
explicit at telling the reader that the default value is an empty list.
Great! This sort of thing is the primary use-case of the feature.

But let's talk about more complex examples:

    def open(filename, mode='r', buffering=-1): ...

How would you make that "more explicit"?

    def open(filename, mode='r', buffering=>(
            1 if 'b' not in mode
                 and os.fdopen(os.open(filename)).isatty()
            else _guess_system_blocksize() or io.DEFAULT_BUFFER_SIZE
            )
        ): ...

Too much information! We're now drowning in explicitness.

Now how do I, the caller, *explicitly* indicate that I want to use that
system-dependent value? I just want a nice, simple value I can
explicitly pass as buffering to say "just use the default".

    with open('myfile.txt', buffering= what? ) as f:

I can't. So in this case, your agonisingly explicit default expression
forces the caller to be *less* explicit when they call the function.

What the right hand giveth, the left hand taketh away.

Can we agree that, like salt, sugar, and bike-shedding, sometimes you
can have *too much* explicitness in code? Sometimes the right level of
explicitness is to have an agreed upon symbol that acts as a short, easy
to read and write, documented and explicit signal to the function "give
me the default value".

And that symbol can be -1, or an enumeration, or a string, or None.

This is not a hack, and its not "implicit".


> None of my comment had anything to do with the caller's perspective.

The caller's perspective is important. The caller has to read the
function signature (either in the documentation, or if they use
`help(function)` in the interpreter, or when their IDE offers up
tooltips or argument completion). The caller has to actually use the
function. We should consider their perspective.


> If my intent is that I want a parameter to default to an empty list, I
> wouldn't assign it to an object called "monkey_pants" by default then add
> code to the body of the function to check if it's "monkey_pants".

Funny you should say that, but *totally by coincidence*, the Bulgarian
word for "default" happens to be transliterated into English as
*monkeypants*, so if I were Bulgarian that is exactly what I might do!
How weird is that?

Nah, I'm kidding of course. But the point I am making is that you are
mocking the concept by using a totally silly example. "monkey_pants"
indeed, ha ha how very droll. But if you used a more meaningful symbol,
like "default", or some enumeration with a meaningful name, or a value
which is a standard idiom such as -1, then the idea isn't so ridiculous.

And None is one such standard idiom.

*Especially* when the default value is not some trivially simple
expression such as an empty list, but a more complicated expression such
as the default buffering used by open files, or in my earlier example of
the default collation (a sequence of letters which depends on the
other input arguments).


> Steven D'Aprano
> "How is it misleading?"
>
> Your way of having a parameter default to an empty list (or whatever) is by
> having the parameter default to None. That's how it's misleading.

All you have done is repeat that it is misleading. I'm still no clearer
why you think it is misleading to document "if the argument is missing
or None, the default behaviour is ...".

Is the documentation false? Does the function not do exactly what it
says it will do if you pass that symbol?

Let's be more concrete.

Do you feel that using -1 for the buffer size in open() is "misleading"?
Do you think that there is ever a possibility that someday Python's file
I/O will use a buffer with *literally* a size of -1?



--
Steve
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@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/SZVP4KBU4XHMML7GQKNKTILC2LU5QOR2/
Code of Conduct: http://python.org/psf/codeofconduct/