Make using immutable datatypes more pleasant by adding a little syntactic sugar

Immutable datatypes like frozenset can help to prevent bugs that might arise from modifying things that shouldn’t get modified. While it is easy to define mutable datatypes like set using curly braces, using a frozenset always requires calling its constructor on an iterable.

my_set = {1, 2, 3}
my_frozenset = frozenset({1, 2, 3})

While this isn’t a problem with such a simple example, it can make your code quite verbose and harder to read when things get more complicated.

Python already introduced f-strings to make string interpolation more pleasant to use. I’m not really a parser guy, so please don’t yell at me if it’s a terrible idea, but wouldn’t it be nice to have something like f-sets?

my_set = {1, 2, 3}
my_frozenset = f{1, 2, 3}  # same as frozenset({1, 2, 3})

AFAIK there are already some plans for a frozendict (immutable map) datatype that could also benefit from providing such a syntax.

It doesn’t flow for me as the f in f{ is nothing like the existing meaning given to string literals by the f in f" - in fact, in some ways they are opposits: f-strings are templates for a multitude of values, whereas frozen, isn’t.

There are two sets created unless you use a cheaper tuple or iterator to create the set:

my_frozenset = frozenset((1, 2, 3))

There’s no need for a Python syntax change for what you want:

def _fs(*args):
    return frozenset(args)

my_frozenset = _fs(1, 2, 3)

What you’re asking for here is a frozenset literal, which has been requested a few times. I’d recommend poking around in the archives to see what has been proposed and what the responses were.

A tuple may be cheaper, but it still creates an intermediate data structure that has to be thrown away.

Not everything can be a builtin with a dedicated syntax, but it is worth considering for the frozen set builtin.

On the other hand, there are circumstances where the CPython interpreter’s peephole optimizer will automatically and silently convert a regular set into a frozenset:

>>> dis.dis("x in {1, 2, 3}")
  1           0 LOAD_NAME                0 (x)
              2 LOAD_CONST               0 (frozenset({1, 2, 3}))
              4 CONTAINS_OP              0
              6 RETURN_VALUE

If we did introduce a frozenset display syntax fs= f{1, 2, 3} there is another point of contention. What should the frozenset repr be? Should it remain “frozenset({1, 2, 3})”, or should it change to use the same display syntax f{1, 2, 3}? The later would make sense but would be backwards incompatible and would likely break doctests and code examples.

The repr for various objects has changed and it’s not considered a critical issue.

rosuav@sikorsky:~$ python3.5 -c 'import re; print(repr(re.I))'
2
rosuav@sikorsky:~$ python3.6 -c 'import re; print(repr(re.I))'
<RegexFlag.IGNORECASE: 2>
rosuav@sikorsky:~$ python3.9 -c 'import re; print(repr(re.I))'
re.IGNORECASE

This sort of change wouldn’t happen in a revision release, so the change in the repr won’t be enough of an issue to cause problems.

One syntax-free option would be for sets to gain a member function that returns an equivalent frozenset. The optimizer could then replace {1, 2, 3}.frozen() with a frozenset literal, without any actual dedicated syntax.

See PEP 351 “The freeze protocol” and PEP 416 “Add a frozendict builtin type” for previous similar discussions.

The frozenset is not used so often, therefore a syntax sugar for that does not justify the complexity added to the language.

1 Like