Shorthand notation of dict literal and function call

It is sometimes tedious to write a dictionary in Python. For example,

def register_user(first, last, addr1, addr2):
    d = {'first': first,
         'last': last,
         'addr1': addr1,
         'addr2': addr2,}
    register(d)

The dict literal above contains a lot of duplicated words and quotation marks.

JavaScript has shorthand notation of property names.

d = {first, last, addr1, addr2}

In Python, {first, last, addr1, addr2} yields set object. So, how about adding a new notation to Python as follows.

d = {:first, :last, :addr1, :addr2}

This is equivalent to

d = {'first': first,
     'last': last,
     'addr1': addr1,
     'addr2': addr2,}

We can use same notation for function call. Following function call in compileall.py:

results = executor.map(partial(compile_file,
                               ddir=ddir, force=force,
                               rx=rx, quiet=quiet,
                               legacy=legacy,
                               optimize=optimize,
                               invalidation_mode=invalidation_mode,
                               stripdir=stripdir,
                               prependdir=prependdir,
                               limit_sl_dest=limit_sl_dest,
                               hardlink_dupes=hardlink_dupes),
                       files)

can be writtern like this.

results = executor.map(partial(compile_file,
                               :ddir, :force,
                               :rx, :quiet,
                               :legacy,
                               :optimize,
                               :invalidation_mode,
                               :stripdir,
                               :prependdir,
                               :limit_sl_dest,
                               :hardlink_dupes),
                       files)

I wrote a simple POC implementation.

https://github.com/atsuoishimoto/cpython/pull/4

Thoughts?

6 Likes

I kind of like it, though I’m not particularly keen on the : notation.

dict.fromkeys() is an existing constructor to initialize a dict from keys. I think I’d rather an alternative dict constructor, e.g. dict.fromvalues()

>>> a, b, c, d = [0, 1, 2, 3]

# Normal dict
>>> dict.fromkeys([a, b, c, d])
{0: None, 1: None, 2: None, 3: None}

# New constructor
>>> dict.fromvalues([a, b, c, d])
{'a': 0:, 'b': 1, 'c': 2, 'd': 3}

where the keys default to the assigned names.

3 Likes
>>> dict.fromvalues([a, b, c, d])

Interesting, but I think this is very difficult to implement in Python.

There was an extensive discussion on this on the python-ideas mailing list not too long ago. I suggest you look at that for a number of comments on this and similar ideas. Sorry, I don’t have a link to hand, but you should be able to find it by searching the list archives.

There was an extensive discussion on this on the python-ideas mailing list not too long ago.

If the thread is this, this is my post on my old proposal.

https://mail.python.org/archives/list/python-ideas@python.org/thread/2UHZWIMHO5GIW7YD3ZT7WLGPVL2LFSK6/#YY7LF6WND7VENMVNNU7GC4OTICPJDYKH

I posted a different notation to the python-ideas at first, but this notation occurred to me in the discussion.

I moved here since I’ve never used discuss and I wanted to try.

Ah, sorry. I didn’t recognise the name.

Hello (I opened an account here just for replying to this discussion, since I am very interested)

How about some notation that resembles the “self documenting expressions”?
https://docs.python.org/3/whatsnew/3.8.html#bpo-36817-whatsnew

f strings can be used like so:

>>> name = 'Santiago'
>>> f'see this variable: {name=} and this expression: {2+2=}'
"see this variable: name='Santiago' and this expression: 2+2=4"

it would be nice to continue on the same track. So short-hand dictionaries could be something like:

>>> name = 'Santiago'
>>> {name=, 2+2=}

equivalent to

>>> {'name': name, '2+2': 2+2}
{'name': 'Santiago', '2+2': 4}

and could even be extended for function calling

def say_hello(name): ...
say_hello(name=)
7 Likes

I’ve been having similar thoughts although motivated by the function call case.

I also prefer the trailing = syntax matching f strings.

Grepping the work codebase, across 130,000 lines we have 1,673 instances of bob=bob type repetition in function calls and 427 instances of "bob": bob type repetition in dict literals.

The frequency of the function call variant is increasing as we encourage the use of keyword args and * to force keyword args. I wouldn’t be surprised if it increases a couple of fold, hence my interest in a less repetitive syntax.

function call re: [^a-zA-Z0-9_]([a-zA-Z0-9_]+)=\1[,)]
dict literal re: \"([a-zA-Z0-9_]+)\": \1[,}]

For contrast the numbers for cpython are 4,060 and 52
And for Django 2,768 and 532
So function calls definitely seem to be the big win.

7 Likes

I also vote for this simplification.

I like the var= shorthand for var=var in function calls. I don’t like the idea of expressions like 2+2= translating to {"2+2": 4} sitting in the kwargs. This doesn’t work now, and it is unsurprising to me that it doesn’t:

>>> def f(**kwargs):
...    print(kwargs)
... 
>>> f(2+2=4)
  File "<stdin>", line 1
    f(2+2=4)
      ^^^^
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
## Not {'2+2': 4}

In f-strings, the expression starts as part of a string and needs to be evaluated, so the {expr=} syntax is right there, conceptually.

Some advantages I see to the var= shorthand:

  1. You get dict(key1=, key2=) for free.
  2. An API changing an argument with a default to keyword-only can be dealt with (if parameters and variables match) by changing f(a, b, c) to f(a, b, c=).
  3. It encourages using variables with names that match parameters. f(long_variable_name=) is cleaner than f(long_variable_name=longvar), which some might prefer to f(long_variable_name=long_variable_name).

(3) might not be seen as a particular advantage, but if I’m constructing a variable for the purposes of passing it to a function, it seems a good practice to match the name reasonably closely. If it’s very long, I will often shorten, as above.

The main drawback I see is that if you start writing f(var=) and forget to finish it, and there’s a var in scope, then you get unexpected behavior instead of a SyntaxError. That said, if I’m passing an argument and have the same variable in scope, that’s typically what I want.

4 Likes

I found this nice write-up from the last time this feature was discussed.

I think I also like the func(var=) syntax. There is a similar feature in ReasonML/Rescript and there it’s written like this:

func(~x, ~y)

but that already means bitwise inversion in Python.

1 Like

Is the goal of this to facilitate writing code or reading code? I’m not sure this is easier to read, and if it’s just writing code, then I recommend using a snippet-manager like UltiSnips.

5 Likes

I unknowingly made a dup of this here: Dict with `=` support similar to f-str `=`

@effigies took this idea to another level from just dict support to signatures in general with f(var=), which I really like. To share some scenarios I think this feature can have real synergy with:

  • Facade interfaces or wrappers
  • Systems with code inspection/reflection

Similar to PEP 618’s strict keyword into zip, this syntax feature tightens up connections, by removing intermediary names.

I would argue this feature can also improve readability because it removes errors/extra thought that can arise from typos or unnecessary renamings.

1 Like

Just is in case somebody else stumbles upon this discussion, here is a little snippet that approximates this proposal:

import inspect

def fdict(**kwargs):
    'fdict(long_var_name=..., foo=bar, lengthy_thingy=...)'
    caller_locals = inspect.currentframe().f_back.f_locals
    for key, value in kwargs.items():
        if value is Ellipsis:
            kwargs[key] = caller_locals[key]
    return kwargs

Ellipsis is used as a sentinel value. This is one area, along with dictionary destructuring, when javascript is more readable than python.

1 Like

Most proposals look something like one of these variations:

return {value1=, value2:"custom", value3=}
return {=value1, value2:"custom", =value3}
return {:value1, value2:"custom", :value3}
return {value1:, value2:"custom", value3:}

I would prefer a syntax like this:

return f{value1, value2="custom", value2}
# because the most common invocation will probably be:
return f{value1, value2, value2}

The f{} syntax takes fewer keystrokes to enter (which is in the spirit of syntactic sugar enhancements) and is also easier on the eyes (consider the final example). It’s also more compatible with JavaScript notation, which helps a polyglot reader.

There is the question of kwargs function calling, where today we use = as the KV separator, and not :, as in object literals. The {value1=, value2:"custom", value3=} syntax is closer to the kwargs calling convention, but I am underwhelmed by how combining equals signs and colons looks. Perhaps the calling convention could be:

myfunc(value1=, value2="custom", value3=)

which would be equivalent to:

myfunc(**f{value1, value2: "custom", value3})

Another reason to like the f{} syntax is that it “pre-declares” your intent to use a new syntax convention in the upcoming object definition. This helps to disambiguate programming mistakes from intent to use the new sugared syntax.

1 Like

I like this idea generally and agree with your reasoning. However, the f for f-strings stands for “formatted”, which doesn’t make sense here. Actually, I don’t know what would make sense.

Thanks for the feedback! I actually just now finished writing a larger proposal on “f-dicts” (f{}), including a discussion on a potential conceptual relationship between the f in f"" and the f in f{}. Some people may feel the connection is not strong enough, but I feel that there is definitely some connection. I don’t want to spam by pasting excerpts of my post here, but if you are still interested, please see my thread and expand the section “Is there any connection between the f in f{} and the f in f-strings?”.

I also love this idea and stumbled on this thread looking for whether Python already had a way to accomplish this. I was happy to discover this thread and Mike Clark’s other thread linked here.

I find myself agreeing with f-dicts but realize it’s because I almost don’t care what this looks like as long as there’s SOME WAY of doing this. Let me throw in a couple… if not “feature requests,” then alternate considerations.

  1. What are the dict keys when I pass an expression like foo.argument_name? I think a sufficiently robust system would be able to handle Name expressions both on their own and in Attribute.attr expressions, but maybe not oddities like tuples or function calls.
  2. Another use case I have for this is unit testing. Specifically, I want to be able to write complicated assertions and then give detailed failure messages. Consider Google’s PyTruth library, if we could capture the expression passed to AssertThat() and then use that expression in the error message instead of making readers pluck it out of the source line. In this case, I would want the full stringified version of each argument, as if this were a C macro.

Now, I hope I’m not poisoning the well by bringing up C macros. But maybe it’s worth considering as we discuss extending Python’s function calling conventions whether we can get more mileage out of allowing a function to accept its parameters as lazy-evaluated named blobs (think pairs of string “argument_expression” and lambda: argument_expression) or perhaps Ast objects and some variety of context. This way,

  1. I could write my own fancy dict packer with whatever insane rules for name resolution I wanted.
  2. I could surface better messages in unit testing frameworks by actually citing the expression being asserted on.
  3. I could defer evaluating parameters to log statements until the log was actually written (so that debug logs wouldn’t do tons of string formatting with logging disabled).
  4. I could avoid evaluating arguments that are conditionally unacceptable (e.g. the function could be called func(foo, foo.bar) and then bail if foo is None without evaluating foo.bar).

Whether #4 is actually a benefit I guess might be a topic of debate… but I trust people to do good and sane things with it.

EDIT: my proposal was a duplicate (thanks to @Nineteendo). I removed it. The idea was:

result = doit(var1, var2, *, mode)
#                         ^
#                         '--- kwargs after this point

It has already been rejected here: PEP 736 – Shorthand syntax for keyword arguments at invocation | peps.python.org

  • For any given argument, it is less clear from local context whether it is positional or named. The * could easily be missed in a long argument list and named arguments may be read as positional or vice versa.
  • It is unclear whether keyword arguments for which the value was not elided may follow the *. If so, then their relative position will be confusingly arbitrary, but if not, then an arbitrary grouping is enforced between different types of keyword arguments and reordering of arguments would be necessary if only one name (the argument or its value) was changed.
  • The use of * in function calls is established and this proposal would introduce a new effect which could cause confusion. For example, f(a, *x, y) would mean something different than f(a, *, x, y).