[Python-ideas] Keyword only argument on function call
Steven D'Aprano
steve at pearwood.info
Sat Sep 8 05:41:50 EDT 2018
On Fri, Sep 07, 2018 at 10:39:07AM +1200, Greg Ewing wrote:
> As for whether consistent naming is a good idea, seems to
> me it's the obvious thing to do when e.g. you're overriding
> a method, to keep the signature the same for people who want
> to pass arguments by keyword. You'd need to have a pretty
> strong reason *not* to keep the parameter names the same.
>
> Given that, it's natural to want a way to avoid repeating
> yourself so much when passing them on.
>
> So I think the underlying idea has merit, but the particular
> syntax proposed is not the best.
But the proposal isn't just for a way to avoid repeating oneself when
overriding methods:
class Parent:
def spam(self, spam, eggs, cheese):
...
class Child(Parent):
def spam(self, foo, bar, baz):
# why the change in names?
...
I agree that inconsistency here is a strange thing to do, and its a
minor annoyance to have to manually repeat the names each time you
override a class. Especially during rapid development, when the method
signatures haven't yet reached a stable API.
(But I don't know of any alternative which isn't worse, given that code
is read far more often than its written and we don't design our language
to only be usable for people using IntelliSense.)
The proposal is for syntax to make one specific pattern shorter and more
concise when *calling arbitrary functions*. Nothing to do with
inheritance at all, except as a special case. It is pure syntactic sugar
for one specific case, "name=name" when calling a function.
Syntactic sugar is great, in moderation. I think this is too much sugar
for not enough benefit. But I acknowledge that's because little of my
code uses that name=name idiom.
(Most of my functions take no more than three arguments, I rarely need
to use keywords, but when I do, they hardly ever end up looking like
name=name. A quick and dirty manual search of my code suggests this
would be useful to me in less than 1% of function calls.)
But for those who use that idiom a lot, this may seem more appealing.
With the usual disclaimer that I understand it will never be manditory
to use this syntax, nevertheless I can see it leading to the "foolish
consistency" quote from PEP 8.
"We have syntax to write shorter code, shorter code is better,
so if we want to be Pythonic we must design our functions to use
the same names for local variables as the functions we call."
-- hypothetical blog post, Stackoverflow answer,
opinionated tutorial, etc.
I don't think this is a pattern we want to encourage. We have a
confluence of a few code smells, each of which in isolation are not
*necessarily* bad but often represent poor code:
- complex function signatures;
- function calls needing lots of arguments;
- needing to use keyword arguments (as otherwise the function
call is too hard to read);
- a one-to-one correspondence between local variables and
arguments;
and syntax designed to make this case easier to use, and hence
discourage people from refactoring to remove the pain. (If they can.)
I stress that none of these are necessarily poor code, but they are
frequently seen in poor code.
As a simplified example:
def function(alpha, beta, gamma):
...
# later, perhaps another module
def do_something_useful(spam, eggs, cheese):
result = function(alpha=eggs, beta=spam, gamma=cheese)
...
In this case, the proposed syntax cannot be applied, but the argument
from consistency would suggest that I ought change the signature of
do_something_useful to this so I can use the syntax:
# consistency is good, m'kay?
def do_something_useful(beta, alpha, gamma):
result = function(*, alpha, beta, gamma)
...
Alternatively, I could keep the existing signature:
def do_something_useful(spam, eggs, cheese):
alpha, beta, gamma = eggs, spam, cheese
result = function(*, alpha, beta, gamma)
...
To save seventeen characters on one line, the function call, we add an
extra line and thirty-nine characters. We haven't really ended up with
more concise code.
In practice, I think the number of cases where people *actually can*
take advantage of this feature by renaming their own local variables or
function parameters will be pretty small. (Aside from inheritance.) But
given the "consistency is good" meme, I reckon people would be always
looking for opportunities to use it, and sad when they can't.
(I know that *I* would, if I believed that consistency was a virtue for
its own sake. I think that DRY is a virtue, and I'm sad when I have to
repeat myself.)
We know from other proposals [don't mention assignment expressions...]
that syntax changes can be accepted even when they have limited
applicability and can be misused. It comes down to a value judgement as
to whether the pros are sufficiently pro and the cons insufficiently
con. I don't think they do:
Pros:
- makes one specific, and (probably unusual) pain-point slightly
less painful;
- rewards consistency in naming when consistency in naming is
justified.
Cons:
- creates yet another special meaning for * symbol;
- implicit name binding instead of explicit;
- discourages useful refactoring;
- potentially encourages a bogus idea that consistency
is a virtue for its own sake, regardless of whether it
makes the code better or not;
- similarly, it rewards consistency in naming even when
consistency in naming is not needed or justified;
- it's another thing for people to learn, more documentation
needed, extra complexity in the parser, etc;
- it may simply *shift* complexity, being even more verbose
than the status quo under some circumstances.
--
Steve
More information about the Python-ideas
mailing list