
On Apr 17, 2020, at 23:18, Steven D'Aprano <steve@pearwood.info> wrote:
Keyword Unpacking Shortcut --------------------------
Inside function calls, the syntax
**{identifier [, ...]}
expands to a set of `identifier=identifier` argument bindings.
This will be legal anywhere inside a function call that keyword unpacking would be legal.
Which means that you can’t just learn ** unpacking as a single consistent thing that’s usable in multiple contexts with (almost) identical syntax and identical meaning, you have to learn that it has an additional syntax with a different meaning in just one specific context, calls, that’s not legal in the others. Each special case like that makes the language’s syntax a little harder to internalize, and it’s a good thing that Python has a lot fewer such special cases than, say, C. Worse, this exact same syntax is a set display anywhere except in a ** in a call. Not only is that another special case to learn about the differences between set and dict displays, it also means that if you naively copy and paste a subexpression from a call into somewhere else (say, to print the value of that dict), you don’t get what you wanted, or a syntax error, or even a runtime error, you get a perfectly valid but very different value.
On the other hand, plain keyword unpacking:
**textinfo
is terse, but perhaps too terse. Neither the keys nor the values are immediately visible. Instead, one must search the rest of the function or module for the definition of `textinfo` to learn which parameters are being filled in.
You can easily put the dict right before the call, and when you don’t, it’s usually because there was a good reason. And there are good reasons. Ideally you shouldn’t have any function calls that are so hairy that you want to refractor them, but the the existence of libraries you can’t control that are too huge and unwieldy is the entire rationale here. Sometimes it’s worth pulling out a group of related parameters to a “launch_params” or “timeout_and_retry_params” dict, or even to a “build_launch_params” method, not just for readability but sometimes for flexibility (e.g., to use it as a cache or stats key, or to give you somewhere to hook easily in the debugger and swap out the launch_params dict.
Backwards compatibility -----------------------
The syntax is not currently legal so there are no backwards compatibility concerns.
The syntax is perfectly legal today. The syntax for ** unpacking in a call expression takes any legal expression, and a set display is a legal expression. You can see this by calling compile (or, better, dis.dis) on the string 'spam(**{a, b, c})'. The semantics will be a guaranteed TypeError at runtime unless you’ve done something pathological, so almost surely nobody’s deployed any code that depends on the existing semantics. But that’s not the same as the syntax not being legal. And, outside of that trivial backward compatibility nit, this raises a bunch of more serious issues. Running Python 3.9 code in 3.8 would do the wrong thing, but maybe not wrong enough to break your program visibly, which could lead to some fun debugging sessions. That’s not a dealbreaker, but it’s definitely better for new syntax to raise a syntax error in old versions, if possible. And of course existing linters, IDEs, etc. will misunderstand the new syntax (which is worse than failing to parse it) until they’re taught the new special case. This also raises an implementation issue. The grammar rule to disambiguate this will probably either be pretty hairy, or require building a parallel fork of half the expression tree so you can have an “expression except for set displays” node. Or there won’t be one, and it’ll be done as a special case post-parse hack, which Python uses sparingly. But all of that goes right along with the human confusion. If the same syntax can mean two different things in different contexts, it’s harder to internalize a usable approximate version of the grammar. For something important enough, that may be worth it, but I don’t think the benefits of this proposal reach that bar.