PEP 736: Shorthand syntax for keyword arguments at invocation

I reviewed a number of past PEPs to see what “how to teach this” normally covers when I was writing this section. What I wrote was designed to cover the same ground as usual but I’d be happy to extend it if helpful and appropriate!

Thanks for your considerate response to my remarks, @joshuabambrick. I really appreciate that.

I suggest considering adjusting your wording.

In the Applicability section of the PEP: “We analysed popular Python libraries […] Based on this, we note […]”.

No, I don’t think there is such data that you could reasonably collect. Since you have no data backing your claim, I would suggest considering rewording that sentence, or make a note that there is no data backing your claim.

Please see Greg’s comment regarding this; I agree with all he said there.

Since this is a claim about the motivation of programmers, the only way to support this claim is to perform a survey.

How would you rewrite this function as multiple independent functions each with a small number of parameters?

That some operations need to accept multiple parameters is a fact of life. You can of course tame the signature complexity using some class abstractions, but the fact stays that you sometimes need/want to accept a non-trivial number of parameters.

4 Likes

Please see my response to Greg earlier in this thread.

Speaking as a Python user since Python 0.9: ugly, superfluous, syntactic sugar…nobody needs this…don’t turn Python into Perl…we already have a bunch of constructs accumulated over the last 30 years that are hard to teach.

7 Likes

That’s perfectly reasonable. My script is available if anyone wants to run it on their own codebases, and I’m happy to take recommendations for corpuses of code to analyze.

3 Likes

Accepting your premise (which I don’t), PEP 736 will encourage an API style that is already prevalent despite not being encouraged, while at the same time providing user-side mitigation for what you call “bad” API design, both existing today and potentially future PEP 736-inspired.

Even if you believed (which I don’t) that library maintainers will start throwing their design principles out of the window because PEP 736 makes keyword parameters more convenient to use, you are saying that having potentially worse not-yet-existing APIs outweighs improved convenience in the many APIs of the keyword-laden variety that exist today. I do not think this is a good trade.

3 Likes

Can you give a link to your script again?

Here it is.

3 Likes

TBH (perhaps just my personal opinion), very few PEPs have written this section well, but also very few have really needed to. We also haven’t strongly encouraged this section until relatively recently, so PEPs older than 3-4 years usually won’t have much at all.

There’s a much higher expectation on a new syntax construct that isn’t simply an extension of something that already exists. e.g. I think PEP 614 (arbitrary decorators) is too brief, but only by a couple of paragraphs, while PEP 636 (the pattern matching tutorial) is right for the scale of that feature.

At a minimum, I think this one ought to cover:

  • how will the code reader/reviewer who’s never seen this syntax before recognise that something unusual is happening
  • what’s the one-line explanation a teacher could give for what is happening here
  • what prior knowledge is required to understand that one-line explanation
  • what prior assumptions are invalidated by learning about this feature[1]
  • what’s the “casual name” for the feature (we’re fairly against calling things “PEP 736 syntax”)
  • is it reasonable to believe that a learner will absorb the concept[2] after having it explained, and why

And given all that: “was it worth the effort?” Which is really a question for the PEP delegate to answer, based on the discussion and consensus that forms as we discuss it here.


  1. This is probably the hardest question of the lot, but I’ll point out that invalidating a past assumption is often a good thing. This case might be “I thought I had to type everything that I meant or the compiler wouldn’t know about it, but it turns out I was wrong!” ↩︎

  2. Or if you prefer, “build a mental model for how it works” or “integrate it into their existing mental model of Python” ↩︎

8 Likes

In the other thread I brought up the point that if ‘explicit is better than implicit’ is so controversial then maybe it shouldn’t be used as an argument in favor of this pep.

In any case, I think it would be good if the “The feature is not explicit” objection section of the pep was rewritten to look more like these quoted paragraphs. The current objection rebuttal:

The feature is not explicit

This is based on a misunderstanding of the Zen of Python. Keyword arguments are fundamentally more explicit than positional ones where argument assignment is only visible at the function definition. On the contrary, the proposed syntactic sugar contains all the information as is conveyed by the established keyword argument syntax but without the redundancy. Moreover, the introduction of this syntactic sugar incentivises use of keyword arguments, making typical Python codebases more explicit.

Suggestions:

  • recognize that “in an obvious sense, the argument value is ‘implicit’ in our proposed syntax” (this clause could be dropped in verbatim).
  • Make the counter-argument that yes, foo(x=) is less explicit than foo(x=x) in exactly the same way that x += 1 is less explicit than x = x + 1 (that is they’re both syntactic sugar: they’re unambiguous, but they save keystrokes by using an implicit notation) so an opponent should at least carefully consider why they’re ok with one but not the other.
  • Finally, restate the point that (1) keyword arguments are more explicit than positional arguments and (2) this pep will make it easier to write keyword arguments.
6 Likes

x += 1 is a different operation - mutating, rather than reassigning. The object contained in x is changed, whereas in x = x + 1 a new object is created and x is reassigned. (Obviously this doesn’t make any difference to immutable types, but try doing d |= another_dict vs d = d | another_dict where d is a parameter and see what happens to the caller.)

From a readability point of view, the amount of sugar is similar, so you could argue that. But you need to carefully point out that the precedent is due to different operations, not merely to reduce typing, and so not “exactly the same way”.

10 Likes

Thanks a lot, I will adapt the PEP to integrate these points in the next edit.

4 Likes

Not sure exactly about the PEP overall, but my own annoyance at repeating arguments has mostly been for super() calls in classes [1]. It may be good to mention that specific case in the PEP, as an example of a case where larger numbers of parameters are perhaps to be expected, and it is often very clear that one is just passing on.

On the syntax, I thought the **, as a separation was actually a nice idea, certainly in the context of super() calls: it separates things that are different and thus more relevant from those that are just passed on.

[1] Specific example: baseband-tasks/baseband_tasks/dispersion.py at 0078122af9ab4dc3c915783b4e9adcb02ae9bf4b · mhvk/baseband-tasks · GitHub

4 Likes

I vary between +0 and -0 on this proposal.

The +0 mindset is based on two key points:

  • in the case that this feature is added, I think the proposed syntax is the right syntax to use (ditto for the equivalent feature in the dedicated dict syntax)
  • agreeing with @gpshead’s point that the “x=x” pattern comes up a lot in communication and data storage code (and this is the primary reason several other languages have comparable syntactic sugar: they’re not offering the feature because it’s “fashionable”, they’re offering it because it’s a recurring need in common problem domains)

The -0 mindset comes from the incorrect assumption in the PEP that changing a local variable name used later in a “x=” construct will always result in NameError being raised. This assumption is only true if the local variable isn’t shadowing a name in an outer scope - in the presence of shadowing, the code will implicitly switch to using the outer variable instead of the renamed inner one. Now, technically the same problem exists even with the explicit “x=x” syntax (hence only being - 0 over this point rather than -1), but that section of the PEP still undersells the potential for confusing debugging sessions when attempting to move some existing code out of using the “x=” pattern.

For the “how to teach this” part, I’d be inclined to teach it and implement it as pure syntactic sugar: keep the AST the same (so most AST manipulation code doesn’t need to care about the new construct, and humans can mentally model it as a pure text manipulation), but add a new boolean attribute on AST nodes that indicates they are implied in the code rather than written out explicitly (that is, “x=x” and “x=” would produce the same AST nodes, but the latter would have a new boolean attribute set on the name lookup node that changed its human readable str form to the empty string)

16 Likes

Thanks @ncoghlan!

Good point, I hadn’t considered this case and will include it. Although I’d really hope that such cases would be very uncommon in practice and a linter would catch them!

For clarity, is there some sense in which you think the proposed syntax makes this more problematic than the existing situation if you were using a keyword argument?

One case I can think of is where you do multiple blocks of similar or the same function calls with argument names that match for something like matplotlib where you procedurally generate a plot and this new syntax may nudge you towards reusing the same variable name, even if that makes the code harder to refactor without making a mistake.

So it’s less about the x= case being more likely to lead to a refactoring bug than x=x, but x= being maybe more attractive (for a long variable name) than x=specific_x and thus encouraging worse coding practices.

2 Likes

Linters are unfortunately more likely to cause the problem than solve it: they complain about the shadowing, then the user misses the implied reference when changing the inner variable name. Linter no longer has a reason to complain, since the shadowing is gone and the reference to the outer variable is valid.

I think the new syntax is slightly easier to miss as a relevant reference when refactoring a variable name. I vary in how much I feel that matters, hence the oscillation between “mostly +0, occasionally -0” (I don’t have any actual data to base a judgement on, so it’s a mental coin flip trading off the common case of more convenient less verbose code in relevant use cases against the occasional rare case of potentially bewildering debugging sessions)

5 Likes

For me, this triggered the question - should the new syntax even allow referencing a non-local variable? Not allowing it would be a very unusual and fairly confusing limitation, but conversely I can’t see a good reason why it wouldn’t be an error to reference a global using the x= syntax.

9 Likes