`frozenset` and `frozendict` comprehensions

I posted very early reference implementation draft here: [WIP] Reference implementation: `frozendict` / `frozenset` literals and comprehensions by sobolevn · Pull Request #152820 · python/cpython · GitHub

1 Like

I’ve always seen list/tuple treated as a mutable/immutable pair of Sequence types. The language of PEP 814 (in a section related to the topic of this thread!) supports this:

We consider that [a dict freezing] method can be added later if needed, but it doesn’t have to be added right now. Moreover, if such method is added, it would be nice to add a similar method for list/tuple and set/frozenset.

3 Likes

This is one of two main list/tuple differences. The other one is that tuples often have a fixed structure. See for instance how the typing module reflects it. Type hints for tuples differ from hints for lists: typing > tuples. For the same reason we have a namedtuple, but not namedlist.

The relationship list/tuple is thus not exactly analogous to set/frozenset case. In my consideration that’s (another) reason against introducing f[...].

1 Like

Adding another literal just for the concept of “frozen” feels a bit tacked on to me, like we’re trying to introduce let into the language but specifically for 2 or 3 types.

I also think prioritizing shorter syntax for the sake of not wrapping is unnecessary. If you have a comprehension that wraps, chances are a separate generator function is more readable anyway.

Some third-party frameworks have their own collection types as well, so frozenset and frozendict aren’t alone in not having unique syntax.

2 Likes

Another possible variation is with a keyword inside the brackets :

{frozen a for a in iterable}
{frozen k: v for k, v in items if foo(k)}
4 Likes

I originally wrote my post mentioning list[T] and tuple[T, ...] as the pair, but felt that the typing syntax was unnecessary to make my point. Clearly I was mistaken, lol

I implemented both versions with f and $ as prefixes for { :slight_smile:

Now, we would have to decide which one is better. There are several concerns for each of them:

  • f
  • Looks like fstring: f{0} and f'{0}', but does a completely different thing: Frozen vs Format
  • In fstring you can use both F and f, it would be strange to allow the same for f{. So, it would be incosinstent
  • It is rather hard to read when these things are nested: f{f'{user}', f'{task}'}
  • It does not scale for other possible similar syntax (which is not a part of the proposal, but just a possibiliy for the future). Because f() and f[] are already valid as a syntax

Now, the second option:

  • $
  • It is a new unfamiliar thing, that people might associate with PHP or BASH
  • It one of the last unused symbols in the syntax

Why not some other options?

  • No identifiers apply because of the scalability concerns
  • No suffixes apply, because it is hard to read
  • No chars that are used in operators apply, because they create ambiguity when parsing
  • No existing valid syntax apply, we can’t break stuff. So, things like {{}} won’t work :frowning:

We are left with very little options, which are reasonable. With this in mind: please, vote :slight_smile:

  • ${}
  • f{}
0 voters

1 Like

IF we add this, I’d vote for f{...} because that has some mnemonic hint and doesn’t introduce $.
But I think frozenset(...) and frozendict(...) offer much clearer mnemonic value, are not that hard to type, and -although incurring some overhead by functional call- will not usually constitute a performance bottleneck. In other words, status quo.
Or just maybe the new soft keyword frozen inside the brackets as per `frozenset` and `frozendict` comprehensions - #46 by hprodh. Unless that also breaks backward compatibility?

I’d prefer frozen{...} or z{...} over f{...} and not ${...}.

9 Likes

frozen{...} has the same problem as f with not being extendable to tuple comprehensions, as an open possibility. For example: $(x for x in range(1)) can be a thing in theory, but f(x for x in range(1)) or frozen(x for x in range(1)) can’t :frowning:

It also is quite verbose and requires a lot of typing.

And the last problem is that it will be quite complex to parse: is it a keyword? frozen { is a thing or not? If frozen{ is a token, then it would be the only token with 7 letters. Currenty we don’t even have an API to parse it :slight_smile:

Moreover, it can cause a confusion in this case:

frozen = 1
x = frozen{1: 2}  # what is this?
1 Like

I really like the idea of treating frozen as a keyword.

I think we shouldn’t worry about tuple comprehensions right now. Frozen sets and dictionaries seem much more important because there’s no literal syntax for them at all. If anything, we can just optimize tuple([i for i in xyz]) to avoid creating a copy (we already do this for frozenset({...})).

I think it’s about as confusing as this:

match = 123
match whatever:
    ...
3 Likes

If I had to choose I find myself stuck between:

  1. It seems a bit much to use an entirely new non-letter symbol (like $%&*@ etc) for just two containers. Looks jargon-y zalgo-y. So I prefer f to $.
  2. But on the contrary we already use f for f-strings and we don’t want confusion with a pre-established concept.
  3. But on the contrary to 2., I would be a hypocrite saying that, as I’m asking in PEP-786 to reuse z from mini-formatting as a truncated two’s complement mode flag for precision-formatted ints, with z’s current only use being to round floats to +ve 0.

What other future uses would $ have wrt immutability? Immutable instances of dataclasses or something? Or just frozenset and frozendict and their comprehensions from the reference implementation?

I get the feeling that if there were a push towards ‘Immutable Python Programming’ as the default then there would be an ‘anti-feature’ creep, losing some metaprogramming and dunders etc in favor of immutability. Semantic issues of immutability becoming borderline syntactic issues, like trying to pass a const char *s to a void may_mutate_string(char *s) in C. Maybe I’m being overly skeptical, as in a somewhat similar way __slots__ classes haven’t eclipsed ‘normal’ classes, but it comes to mind.


Anyways is it not possible to skip this entire thread’s premise of adding ‘external’ user-facing syntax by just internally optimizing frozenset({a, b, c}), and frozenset(my_set) when there’s only one reference left to my_set which is being passed to frozenset? Can cpython not tell that there’s only one reference and assume ownership of the set’s contents? Like:

There should already be a couple of branches following the parsing of the curly braces, to handle the distinction between dictionaries and sets. Thus having frozen inside the brackets (right after the opening one) would allow to have the frozendict and frozen set branches at the same place I think. Am I right ?

frozenset { 1, 2, 3 } and frozendict {1: 1, 2: 2, 3: 3} remind me of the struct literal syntax[1][2] which I think is a point in favor. Although it will feel out of place if it’s there only for two types.


  1. A Tour of Go ↩︎

  2. struct - Rust ↩︎

2 Likes

I was actually going to suggest trailing {} for a new dunder method, mildly based on Swift’s trailing closure syntax. It would give both builtins like frozenset and third-party frameworks syntactic sugar for initializing collections.

But honestly, I just don’t think that frozenset{1, 2, 3} is that much better than frozenset({1, 2, 3}).

fset{}
fdict{}

:thinking:

There already exists property.fset, that’s what I’m reminded of.

2 Likes

Small summarization of some comments, even if there is no clear general consensus on particular points:

  • putting the “freeze modifier” before data e.g. frozen {...} is better than after {...}.freeze():
  • short is better than long typing
  • words/letters are preferred over cryptic special chars, e.g. ${}
  • one token frozen{ ... } is better than two: frozen=1 ; x = frozen {...}
  • new keyword must be justified and it probably isn’t in this case

So far, the original proposal of f{k:v for ... } is doing quite well according to these criteria.

I’d like to propose its small variation f_{k:v for ...} for better readability. It also differs from f"text".

4 Likes

If we are considering more than one character ,fr{…}, fz{…}, frz{…} are also in the option space, and I’d find a shortened „frozen“ more intuitive than adding an underscore.