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.

2 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.

3 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.

4 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.

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?”.