Continuing the discussion from Syntactic sugar to encourage use of named arguments, where I created a tangent on dictionaries:
Creating this new topic to not dilute the discussion on keyword arguments with a discussion on dictionaries.
Continuing the discussion from Syntactic sugar to encourage use of named arguments, where I created a tangent on dictionaries:
Creating this new topic to not dilute the discussion on keyword arguments with a discussion on dictionaries.
Thanks Chris, so still around 10% in the stdlib by that metric. I definitely have a higher percentage than that in my own code, above 50% if the project I’m looking at right now is representative.
For the syntax, while I do quite like func(keyword=)
, I would also like func(:keyword)
, which looks a bit “neater” to me without the dangling equal sign. The motivation comes from [1] the related problem of specifying dictionaries. Given that {keyword}
creates a set, I think the best we could do is abbreviate {"keyword":keyword}
as {:keyword}
, since the variable should always go after the colon. This leads to a natural equivalence:
func(keyword=keyword)
func(**{"keyword":keyword})
func(**{:keyword})
func(:keyword)
But I accept that this feels like much more to take in than just an abbreviation for keyword arguments, so it’s probably not worth discussing.
and to make any sense at all would require also tackling ↩︎
Yeah, that’s a much tougher pitch IMO. There isn’t a strong enough parallel unless the :keyword
syntax exists. But that said, I do think the {:variable}
notation would be a perfectly viable parallel proposal; I just don’t think it would be worth modifying this one for it.
Is dict(keyword=)
not good enough?
Where the shorthand notation really shines is in extracting variables from APIs that return dictionaries (yet another proposal!), which unfortunately I have to deal with quite a bit. So this is what I see in my wildest dreams:
{:spectrum, :noise} = some_external_code_that_likes_dicts()
But now we are three separate proposals deep, so dict(keyword=)
is great.
Right, but without the dictionary unpacking, you’ve already got something similar with a structural pattern matching?
match some_external_code():
case {'spectrum': spectrum}:
...
But I can see how a dictionary notation might be useful here.
No, because that expands to dict(keyword=keyword)
, not dict(keyword=“keyword”)
Are you sure that’s not what he wants? Correct me if I’m wrong, but what he wants is dict(keyword=keyword)
, and he’s using the notation {:keyword}
as shorthand, which you can see here:
Oops, sorry, you’re right - I misread.
No worries, it always takes time to get used to new notation
Now that this tangent was split off into its own discussion, I want to answer this point with another advantage of the dedicated syntax: dict(keyword=keyword)
is a regular call to the dict
object, but {"keyword": keyword}
is a specialised call to construct a mapping:
>>> dis.dis(lambda: dict(keyword=keyword))
1 0 LOAD_GLOBAL 0 (dict)
2 LOAD_GLOBAL 1 (keyword)
4 LOAD_CONST 1 (('keyword',))
6 CALL_FUNCTION_KW 1
8 RETURN_VALUE
>>> dis.dis(lambda: {"keyword": keyword})
1 0 LOAD_CONST 1 ('keyword')
2 LOAD_GLOBAL 0 (keyword)
4 BUILD_MAP 1
6 RETURN_VALUE
Therefore, even if the dict(keyword=)
shorthand notation existed, I would probably not opt for it to construct dicts.
One missing piece here is dict comprehensions, which might be a little confusing. I don’t think it makes sense to support them, but people might think they can write:
values = [foo, bar, baz]
value_dict = {:v for v in values}
And get {"foo": foo, ... }
, but it should result in {"v": baz}
which nobody wants. So dict comprehensions shouldn’t support this but it breaks the equivalence a little bit.
Please stop while you’re ahead. The keyword proposal hasn’t even been written up as a PEP. This dict syntax feels much less natural. Let’s not turn Python into Perl.
I once thought about making this less cumbersome, but failed pushing it further. My idea was a new syntax for destructuring patterns in a single line, instead of these three lines with lots of boilerplate:
match some_external_code():
case {'spectrum': spectrum}:
pass
I suggested match some_external_code() with {'spectrum': spectrum}
. @Rosuav told me this had been discussed before, with suggested syntax like {'spectrum': spectrum} = match some_external_code()
.
My personal feeling is that saving a line of code on uncommon patterns is not such a big deal.
I personally like the two recent proposals that affect a great many lines of code:
x otherwise 2
for 2 if x is None else x
, andf(value=)
for f(value=value)
.If these were rare patterns, I wouldn’t be as positive about them.
Almost every time I see discussions about more syntactic sugar, I want to read over The Zen of Python again.
Most of these proposals simplify writing code, but they tend to go against several of the above mentioned principals.
I’m reminded that I still hate Perl despite not using it for close to 20 years.
Which ones? Syntactic sugar usually epitomizes “Beautiful is better than ugly” and “Simple is better than complex”, and generally benefits “Readability counts”.
Lemme guess, you’re going to cite “Explicit is better than implicit”? That has to be the single most cited and mis-cited line in the Zen.
Actually I am most drawn Readability counts and Special cases aren’t special enough to break the rules.
With more than 20 years of intensive experience with Python and over 40 years of software development, I know that most of a piece of software’s lifetime is spent in maintenance. And that software maintenance is not the most exciting, rewarding, or senior phase of its lifespan. More junior staff is often tasked with doing that, and they may not have as deep an understanding of the language and definitely not as deep an understanding of the application as the senior developers who did the design and initial implementation (i.e. the more creative parts).
I do use keyword arguments, but like so:
func1(**{"keyword":1})
func2(**{"keyword":True})
and in func1
, or in one of its callees, I use something like one of:
if hasattr(kwargs, "keyword"): local_var1 = True
local_var2 = getattr(kwargs, "keyword", False)
local_var3 = kwargs.get("keyword")
The type and content of value
do not matter, as long as bool(value)
evaluates to True
.
And what is frequently forgotten is that syntactic sugar is what makes this at all reasonable. Every programming language is full of it. Be careful of the assumption that what you already know is intuitive and what you don’t already know is obscure.
Remember that what is new today will no longer be new in a few years, and what is perfectly normal and common today was once new. Would you have objected to async functions? They’re really just syntactic sugar for generators and an event loop. What about “yield from”? That’s one of my favourite pieces of syntactic sugar, partly since its pep includes a full expansion of its semantics, so you could easily replace it with what it fundamentally stands for. Does “yield from” hurt or help readability? It’s just syntactic sugar, remember.
I feel like this would be a really positive improvement for python.
To look at some other languages, TypeScript already has this in some places (like when constructing objects). It doesn’t detract from readability there, but I will admit that it does take a double take the first time you see it if you don’t know that it happens.
The dict sugar is another thing entirely, however, if you’re going to add this syntax for kwargs, having it work across places that “make sense” is a good idea for consistency. the :
operator does however really look weird and out of place to me in functions. but =
would look out of place in dict literals. So I think I agree with Guido here, lets lean towards just a kwarg change. We can always recommend a more general change to dict literals etc later if this is more generally liked.