`frozenset` and `frozendict` comprehensions

I think that f{} is the most simple and mnemonic solution proposed until now. It’s only one letter, it’s the initial of frozen.

I don’t see the ambiguity with format strings. Honestly, I never read f'{}'. At most, f’{}{}', and IMHO it’s hard to confuse it with f{}. If so, also * is ambiguous because you can write 6 * 9 but also 6 * "oh" :wink:

We can also put a c for cold, but I don’t think the people will remember it easily. Or k0, for zero Kelvin. Cygnus will be happy.

He’s going for the righteous indignation dollar. That’s a big dollar. A lot of people are feeling that indignation. We’ve done research – huge market.

Bill Hicks. Please no dollar sign :rofl:

Side note: just a joke. Whatever symbol you decide, I hope it will be accepted.

3 Likes

An alternative to changing the Python grammar for a new syntax is to add new efficient dict and set methods to convert to frozendict and frozenset. See Add a dict method to convert to a frozendict? discussion.

Regular dict and set are used to build a collection, and then .take_frozendict() or .take_frozenset() freezes the object (convert to frozendict/frozenset). These functions clear the temporary dict/set.

For example, frozendict({'x': 1, 'y': 2}) can be written {'x': 1, 'y': 2}.take_frozendict(). So instead of adding a prefix (multiple prefixes have been proposed so far!), it does add a… suffix :slight_smile:

Some examples.

Frozendict literal, when frozendict(key=value, ...) syntax cannot be used:

-_capability_names = frozendict({
+_capability_names = {
     _curses.KEY_A1: 'ka1',
     _curses.KEY_A3: 'ka3',
     _curses.KEY_B2: 'kb2',
     ...
-    })
+    }.take_frozendict()

Frozenset literal:

-    _ID_KEYWORDS = frozenset({"True", "False", "None"})
+    _ID_KEYWORDS = {"True", "False", "None"}.take_frozenset()

As a bonus, it can be more in more cases, like convert a list-comprehension to a tuple:

-        return tuple([_asdict_inner(v, dict_factory) for v in obj])
+        return [_asdict_inner(v, dict_factory) for v in obj].take_tuple()

Or convert a set-comprehension to a frozenset:

-_ATTRIB_DENY_LIST = frozenset({
+_ATTRIB_DENY_LIST = {
     name.removeprefix("assert_")
     for name in dir(NonCallableMock)
     if name.startswith("assert_")
-})
+}.take_frozenset()

See my pull request for more examples. It also adds list.take_tuple() method.

1 Like

It seems work on a method / suffix is progressing well, and I think that’s my preference

1 Like

Honestly. I dislike all of the symbols and short forms suggested in this thread. When it comes down to it, I fundamentally don’t think frozendicts and frozensets are important enough to the language to be deserving of dedicated syntax you have to recognize and memorize.

If we’re looking for syntax that can be detected and optimized, frozendict(i: i+1 for i in range(10)) makes the most sense to me. It’s not currently valid syntax, but it looks just like list(i+1 for i in range(10)), which is valid.

2 Likes

I like the idea, but the two proposal can coexist. Indeed we already have list comprehensions and you want to add also list.take_tuple().

2 Likes

but the two proposal can coexist

They do this really well! You can convert some existing mutable objects to immutable forms with .take_frozendict, .take_frozenset, etc. And you can define new ones with the new syntax :slight_smile:

3 Likes

I’m noticing I dislike these specific code changes. Seeing the type announced at the start of the block as in tuple([_asdict_inner(v, dict_factory) for v in obj]) feels more natural than seeing a “convert to tuple” method at the end.

I struggle to see any benefit of those syntaxes vs a generator comp wrapped in the class constructor.

4 Likes

@sobolevn, @vstinner , and I are preparing a PEP to introduce frozen display syntax, as it would enable compiler optimizations and make frozen containers easier to handle throughout the optimization pipeline.

11 Likes

While drafting the PEP and preparing the implementation, I have personally preferred the f prefix because it naturally evokes the family of frozen types and should not introduce any ambiguity for the parser.

On the other hand, if we view this as syntax for immutable containers more generally, what about using an i prefix instead? (For people who want to distinguish from f-string explicitly )

i{1, 2, 3}
i{1: 3, 5: 6}

This would also leave the i prefix available for other potential immutable types. The main drawback is that i{1, 2, 3} would still create a frozenset, rather than a type called immutableset.

In any case, the main reason I prefer introducing new display syntax is that it would allow us to add dedicated opcodes such as BUILD_FROZENMAP and BUILD_FROZENSET. This would integrate frozen containers cleanly into the optimization pipeline and open up more optimization opportunities. Without dedicated syntax and opcodes, similar optimizations would likely require more ad hoc approaches. (While I experienced for this: Fold _BINARY_OP_SUBSCR_DICT for promoted frozendict constants · Issue #146381 · python/cpython · GitHub)

If we want to encourage broader use of frozendict and frozenset for more safer Python, providing convenient syntax and direct compiler support would also be an effective way to promote them.

12 Likes

I couldn’t figure out what i stands for until I reached the place where it is spelled out.
At least to me, i primarily suggests “iteration”.

2 Likes

Tongue-in-cheek: f-strings actually are frozen strings. f"spam" is a frozen version of "spam", even if the latter is already frozen itself.

Anyways, I still weakly prefer f over i. ffrozenset makes much more sense to me than i → “immutable” → frozenset. I’m personally not too worried about frozen/f-string confusion—in part because LSPs exist, in part because I personally think f{"..."} and f"{...}" actually look pretty different, and in part because for actual use cases, the contents of an f-string and the contents of a frozen set will usually be different enough to be apparent. f"{spam}" and f{"spam"} are the most confusing cases, but the former would usually just be str(spam), and making a frozen set out of a single string literal seems rare (though not unthinkable). Outside of those two constructs, f-strings will usually look more like f"{spam} {ham} {eggs}", while frozen sets will look more like f{"spam", "ham", "eggs"}, and I (again, personally) find those two fairly easy to distinguish.

5 Likes

Well, if people really dislikes f{}, i{} is good enough. I suppose frozen was chosen because immutable is too long. i is mnemonic anyway and handy, and can resolve the impasse.

Maybe is it time for yet another poll? We can use the STAR vote[1].


  1. Just joking :stuck_out_tongue: ↩︎

1 Like

There’s also some discussion on the notation in the PEP proposal thread PEP 841: Adding Frozen Syntax to Make Immutable Types Optimizable - #48 by blhsing

As said in PEP 841: Adding Frozen Syntax to Make Immutable Types Optimizable - #50 by timhoffm, it’s generally a good idea to decouple

  • whether frozen literals provide enough benefit
  • what would be a good syntax if one wanted to add frozen literals

If we do a poll for the second, we should have a shortlist of reasonable candidates to choose from. I propose:

Consider:

  • f{"a", "b"} - short but a bit similar to f-strings
  • i{"a", "b"} - short and not similar to f-strings, but maybe immutiable is a slightly worse mnemonic because the object is called frozen not immutable
  • {|"a", "b"|} - a two-char bracket not a prefix-modifier

Maybe consider:

  • frozen{"a", "b"}, fr{"a", "b"}: longer word prefixes - may be easier to understand and discern from f-strings, but are more verbose
  • {|"a", "b"} - an “inner” prefix-modifier - a bit unusual but not a no-go

Do not consider:

  • ${"a", "b"}: no new symbol
  • |{"a", "b"}| or +{"a", "b"}: no symbols that are operators outside the braces
  • {{"a", "b"}}: already valid syntax

Overall, any syntax has to be learned, so the key aspects are how easy it is to identify (discern from other code constructs) and to remember.

1 Like

I think this would be amazing to have. I’ve been in charge of several initiatives at $DAYJOB to switch to immutable data structures to help concurrency issues, and they have always been accompanied by a slight reduction in readability/elegance. (We still did them, of course.) Having literals and comprehensions would help a lot.

The exact prefix is unimportant to me personally. I’d be happy with anything.

4 Likes