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?

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

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