PEP 736: Shorthand syntax for keyword arguments at invocation

Yes it should allow it. ‘Foo(x=)’ should be EXACTLY the same as ‘Foo(x=x)’ in all cases. As soon as special exceptions are made it immediately becomes way too confusing to think about and use. I would much rather not have the feature than have anyone have to remember any special cases.

14 Likes

If I understand correctly, are you proposing that the “PEP 736 syntax” should be scope-based?

No, I’m suggesting that it’s a potential usability issue that the proposal can silently pick up a global variable rather than a local one. Of course, this problem is no different than what happens if you use x=x without a local variable x, but the implicit[1] link to the variable named the same as the argument makes it a bit harder to spot.

That’s all.


  1. I’ve tried not to use the term “implicit”, because of all the arguments over quoting the Zen. But I’m fed up of contorting my words just to avoid a perfectly good term. Let’s just accept that I’m not trying to bring “explicit vs implicit” into the discussion, shall we? ↩︎

7 Likes

Yes, that’s the same issue I described in the parent thread.

This is not an issue with named arguments; the argument is passed explicitly, not implicitly.

I usually rename a variable-to-be-deleted to a dummy name, then to an empty string. The callee raises NameError exceptions in the next run. In other words, the code is incorrect if the variable after the equal sign does not exist. This has been very helpful to avoid causing havoc.

This method of mine would not work with the proposed syntax; both renaming and replacing the variable with an empty string would not work. Additionally, I have not had a positive experience with refactoring features provided by IDEs.

Edit: Corrected wording in bold.

1 Like

I don’t understand what you mean by this. You can’t rename variables to “” afaik?

Find and Replace

But that won’t give you nameerror. That will result in syntax errors!

1 Like

I didn’t mention ‘Find and Replace All,’ and we are off-topic.

Edit: I corrected the wording in my previous post to avoid confusion. I was thinking about where the ambiguity came from.

1 Like

As you point out, foo(x=x) can “silently pick up a global variable rather than a local one” just like foo(x=) can. The issue feels slightly more pernicious because of the implicit auto-filling of the variable name but I would say that this issue is not particular enough to call out. (1) it is slightly pernicious that a variable can be picked up from a global scope (this is just how python works) and (2) it is slightly pernicious that this PEP picks up variables without actually typing them out (because they are inferred from the parameter name). I think the perniciousness of foo(x=) picking up a variable from the global scope is just equal to the SUM of the perniciousness of (1) and (2). That is, it’s not extra pernicious.

The point out ctrl-f or ctrl-r is kind of interesting. It does seem that using f(x=) will need to change how we do ctrl-f or ctrl-r when refactoring variable names but I’m not convinced that that is true, again, following the logic that foo(x=) is literally just shorthand for foo(x=x). If we have foo(x=x) and we want to replace x->y in only the calling scope then we can’t just do a blanket ctrl-r replace x by y because that would result in foo(y=y). This is the wrong behavior because now we’re binding y in foo, not x. So in this case we already need to take care how we do ctrl-r. Of course if we’re replacing x->y in both the calling and callee scopes then a blanket ctrl-r works fine. In all cases (including with and without this PEP) an IDE refactor/rename tool is able to easily replace the appropriate instances.

Suppose we have foo(x=) and then we decide to rename x->y in the calling scope. I would expect the IDE refactor tool to replace the function call to be foo(x=y). If we replace x->y in the callee scope I would expect foo(y=x). I don’t think IDEs do or should support refactor tools that simultaneously replace variable/parameter pairs in both the calling and callee scopes so we don’t need to worry about that case (ctrl-r can cover that case if both scopes are within the same project).

1 Like

I expect the shorthand syntax to be more usable in IDE-less environments, e.g., Jupyter. Based on these stats and my own experience, Python, being a scripting language, is IDE independent. I don’t usually use an IDE to code in Python, except for very complex projects. Hence the Ctrl-F/R ninja skills. :ninja:

In an IDE, the autocomplete feature defeats the purpose of the shorthand syntax.

My claim is that the Ctrl-F/R ninja skills workflow is not impacted by PEP 736. If you’re working in the calling and callee scope and want to replace the variable name and parameter name you can do a blanket Ctrl-R and you’re good. But if the variable name and parameter name are matched BUT you only want to change one of them, you already need to do some filtering on your Ctrl-F/R to make sure you don’t modify both the parameter and the variable name.

Again, my point is the Ctrl-F/R workflow is not impacted/made more difficult by PEP 736, but open to hearing an example to the contrary.

1 Like

The impact is just another regex to match the parameter name in the shorthand syntax and add the variable name when renaming the variable or a dummy name when deleting the variable. That will ensure that no other variable from parent scopes is passed implicitly.

The difficulty lies in using regex to avoid manual editing. Without the shorthand syntax, you don’t need any manual editing; just click Replace while checking.

Hard disagree.

This syntax makes it much clearer when code deviates from a pattern. When x=y for example. Especially in long lists of x=x it is very hard to see the special case of x=y because it’s hidden in the noise.

6 Likes

Now, there’s an actual objective positive impact of this proposal. It should make its way to the PEP.

13 Likes

Here’s a blog post about the (roughly) same feature in Ruby. The implementation, the tradeoffs and the results. “Useless Ruby sugar”: Keyword argument and hash values omission

I think this pretty well tracks what I would expect the result to be, and (in my opinion) quite an endorsement of the feature.

9 Likes

This was really interesting, thanks for sharing

1 Like

How to teach this

Here’s my thoughts for both the * syntax (which I prefer), but also x= syntax, which I’m becoming warmer to.

  • (* version) Using only special characters in place of an argument screams “something unusual is about to happen”, just as it did the first time I read a function signature with / and *.
    • (x= version) equals what? This isn’t how an equals sign is used in any other place, so something unusual must have happened.
  • (* version) “Everything that follows is passed as a keyword argument, but the called function’s keywords and values are the same as the variable names and values in the caller.”[1]
    • (x= version) “This is a keyword argument, but the called function’s keyword and value is the same as the variable name and value in the caller.”
  • what a keyword argument, variable, and function are.
  • The most simple understanding of function calls is that everything between commas is an expression, and that expression=argument==parameter slot. Learning this syntax invalidates the misconception that arguments are evaluated and then assigned to parameters as-is, without any special meaning handled by interpreter. This is the same misunderstanding also invalidated when one learns splatting and varargs.
  • “(Automatic|Identical)+ Keyword Assignment”
  • Yes. Basically, I think the difference between “about to happen” vs “just happened”, the sorting enforced by *, and the similar explanation to function signatures are why I think * or ** is easier than x=, but that’s obviously open to interpretation.

  1. if regular kwargs may follow, then this should be “Everything without an = that follows…” ↩︎

1 Like

Would this be something to include in the PEP as a supporting link? Seems like a relevant read/addition to me.

Mind, I’m still not solidly +1 on the proposal overall, but I am in favor of the PEP being as strong as possible with all the best arguments and evidence, whether for or against.

5 Likes

Usefulness of this proposal:

  • high, because for developers coming from JavaScript/TypeScript, the lack of a shorthand syntax for creating dictionaries (or objects where the keyword arguments to the constructor match the names of the attributes, a la dataclasses/attrs/pydantic) is actually quite confusing. In many other languages, being able to do something like { x, y, z } instead of { x: x, y: y, z: z } is simply table-stakes syntax.
  • code that takes in a set of arguments and ends up passing it to a downstream function with the same argument names really is common. In some cases it’s a sign of something that needs to be refactored, but in other cases it’s simply the most straightforward to compose code. Right now this can feel very verbose in Python, and all the extra words obscure the intent of the code.
  • this is particularly relevant in typed Python, where the use of dynamic kwargs dictionaries becomes a liability - Python with types is always going to be more verbose than Python without, but to the extent that we can mitigate this with healthy syntactic sugar, it seems like we’ll be making “modern” Python more pleasant to read and write.

Reasonableness of the proposed syntax:

  • = makes a lot more sense in Python than : or %, etc.
  • I would personally prefer the f(a, b, *, x) syntax, mainly because in a long list of arguments, it will get redundant to specify = a bunch of times. I think order-dependent arguments are unavoidable in Python (it’s a syntax error to specify a positional argument after a keyword argument), and yes, these are not always explicit and it does require reading carefully. But overall, the semantics are unambiguous and I don’t think this would be a step backward from the way Python also operates.
  • overall, I think the usefulness of the feature outweighs bikeshedding about the syntax, and I am strongly in favor of this PEP as-is.
3 Likes

I don’t want to restart any debate on the proposed syntax, only note that the lone keyword-based syntax that appeared in the poll (f(pass x)) isn’t mentioned in the “Rejected Ideas” section.

Also, the “Prior Art” section could make note of its similar use in Nix when defining an attribute set, alluded to in a November post (Syntactic sugar to encourage use of named arguments - #129 by emanueljg). Something like

In Nix, {inherit x; y=3} is shorthand for {x=x; y=3}.


In Nix, multiple variables can be inherited with a single inherit declaration,

{inherit x y; z=5} == {x=x; y=y; z=5}

Something similar in Python might look like f(inherit x y, z=5). There is a little more to the syntax in Nix, but any further mention here would start to look like advocacy for using it :slight_smile: . If any mention of the syntax is deemed useful for the PEP, I refer the editor to Language Constructs - Nix Reference Manual.

4 Likes