"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.