I posted very early reference implementation draft here: [WIP] Reference implementation: `frozendict` / `frozenset` literals and comprehensions by sobolevn · Pull Request #152820 · python/cpython · GitHub
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.
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[...].
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.
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)}
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 { ![]()
f: Comparing main...c9004e5977a05d5ac644e1741adc446af96d0f29 · python/cpython · GitHub$: https://github.com/python/cpython/pull/152820/commits/657fff2f18a84601a180f1be74876c537fc9b7eb
Now, we would have to decide which one is better. There are several concerns for each of them:
f- Looks like fstring:
f{0}andf'{0}', but does a completely different thing: Frozen vs Format - In fstring you can use both
Fandf, it would be strange to allow the same forf{. 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()andf[]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
We are left with very little options, which are reasonable. With this in mind: please, vote ![]()
- ${}
- f{}
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 ${...}.
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 ![]()
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 ![]()
Moreover, it can cause a confusion in this case:
frozen = 1
x = frozen{1: 2} # what is this?
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:
...
If I had to choose I find myself stuck between:
- 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 preferfto$. - But on the contrary we already use
ffor f-strings and we don’t want confusion with a pre-established concept. - But on the contrary to 2., I would be a hypocrite saying that, as I’m asking in PEP-786 to reuse
zfrom mini-formatting as a truncated two’s complement mode flag for precision-formatted ints, withz’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.
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{}
![]()
There already exists property.fset, that’s what I’m reminded of.
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".
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.
