[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