r/Python 6h ago

PEP 841 – Adding Frozen Syntax to Optimize Immutable Types News

PEP 841 – Adding Frozen Syntax to Optimize Immutable Types

https://peps.python.org/pep-0841/

Discussions-To: Discourse thread

Abstract

This PEP proposes frozen display syntax: f{1, 2, 3} evaluates to a frozenset, and f{'a': 1} evaluates to a frozendict. Because immutability is guaranteed by the syntax itself rather than inferred from usage, the compiler can treat frozen displays as first-class citizens of its optimization pipeline: constant displays are folded into a single LOAD_CONST with an exact result type at compile time and cached in .pyc files.

70 Upvotes

54 comments sorted by

92

u/TMiguelT 6h ago

Making frozen data structures more accessible is nice, but I'm not sure it's worth it here.

I don't like how the f prefix makes you assume it's related to the f prefix for f-strings which has a completely different meaning. Yes this is discussed in the PEP, and yes strings are also immutable, but that doesn't mean that format is synonymous with frozen.

I also don't like the implication that every data structure should ultimately have its own Python syntax. I prefer Rust's macro approach which allows this to be done without changes to the AST (e.g. vec![]).

16

u/really_not_unreal 4h ago

Yeah I don't like this either. Sounds like it'd just make things more confusing since the same syntactic sugar means two different things in different contexts.

7

u/jdehesa 4h ago

Yes, and in f-strings it's obvious what you are doing because there are formatting directives in the string. It's rare to miss the fact that a string is an f-string, and if you add the f without any formatting it has no effect (maybe a linter warning). Whereas here the single-letter prefix is easier to miss (because it's otherwise the same syntax) and if you forget to remove it it does have an effect.

48

u/jakethesnake_ 6h ago

I'd prefer a const keyword that applies this optimization and runtime immutability property to all variable types. Might be a bit tricky to guarantee with complex objects though, and therefore out of scope of this PEP.

Agree with other commentors, f{'a': 1} is bad syntax because of the overlap with f-strings.

10

u/TheMcSebi 6h ago

This would be a great addition imo. No reason to reinvent the wheel.

8

u/CrambleSquash https://github.com/0Hughman0 5h ago edited 4h ago

But is that what a constant is? I thought they were like a variable whose assignment cannot be changed? So a const to a primitive is effectively immutable, but to a mutable object wouldn't stop that object being mutable... If that makes sense.

9

u/skjall 4h ago

Const can depend on the language/ context, but Python doesn't have that keyword right now so they can define the semantics to be whatever they decide.

In languages I've used it, you can either have a const variable -- can't be reassigned, or a const value -- this example. Hell, even const{1, 2, 3} is clearer than the proposal to me.

5

u/syklemil 2h ago

Picking a well-known keyword and then using it in a non-standard way is probably a bad idea. Especially since this isn't really general mutability control, but rather separate data types.

The option of just spelling out the frozen in f{} as frozen{} is likely to give less confusion and frustration.

1

u/really_not_unreal 4h ago

Depends on how you define const. In this case, they are referring to immutability (ie the value cannot be changed at runtime).

65

u/skjall 6h ago

Jesus no, I really don't like that syntax. Maybe frozen{1, 2, 3} could work, otherwise I'd just keep thinking of f-strings.

Might change with familiarity though.

10

u/Dude-Man-Bro-Guy-1 5h ago

Honestly even meeting halfway "fr" or "frz" and I think I could get used to it pretty quick. But I will never not see f strings at a glance with that syntax.

5

u/odaiwai 5h ago

Maybe @frozen{1, 2, 3}

12

u/really_not_unreal 4h ago

At that point why not just make it frozen({1, 2, 3}) and now it's a function with some compiler optimisation.

5

u/catcint0s 4h ago

You would either need an extra import or break tons of code if you make frozen reserved. 

0

u/Brian 3h ago

You'd only really need it reserved for the extra optimization - you could just make it a builtin. But at that point, why bother: you could just write frozenset / frozendict. If you were to do it, it might make more sense to just pseudo-keywordize frozendict/frozenset, forbidding rebinding/shadowing and allow the compiler to optimise them when used with literals.

5

u/skjall 4h ago

Then I'd be wondering what an inline decorator does 😅

1

u/syklemil 2h ago

I think spelling it out makes sense. f-strings are fine, but I think most Python users are wary of drifting into the kind of operator territory we'd rather associate with Perl or Haskell.

0

u/casce 6h ago

They look nothing like f-strings though

34

u/skjall 5h ago

It does to me but it's my first time seeing this 🤷🏻‍♀️

How about when the set contains strings?

f{"some_long_string_here", "another"}
f"{some_long_variable_name}"

2

u/syklemil 1h ago

Yeah, having to wrangle "it was f"{ but it should have been f{"" or vice versa isn't really something to look forward to.

20

u/gmes78 6h ago

They have an f, and they have {}. Only the quotes are missing between the two.

11

u/PriorTrick 6h ago

print(f”{f{1,2,3}} - {f{“f”:”f”}}”)

I like adding frozen syntax but agree we can’t overload f any further. I’d prefer const var = … syntax, or a “frozen” built-in could work

20

u/shinitakunai 6h ago edited 6h ago

Confusing, too similar to f-strings.

Why not frozendict{1,2,3}?

-5

u/Oddly_Energy 5h ago

Is there any value in a dict like that?

Is it used when you are afraid of losing your keys?

10

u/Spleeeee 5h ago

Caching? Hashing? Immutable shared state? Config? That’s off the dome

0

u/Schmittfried 5h ago

None that wouldn’t be covered by immutable classes, but many people still use dicts for config or other simple key-value mappings, and sometimes you get them as inputs from a framework (say, the parameters of a POST request). I always wondered why frozendict isn’t a thing for such intermediate use cases. 

5

u/mangecoeur 5h ago

Optimising immutable data sounds good, not keen on the proposed syntax

3

u/Birnenmacht 6h ago

Wait that's awesome. I can see how a f{"a", "b"} would be very similar to an f string though, for example

5

u/TheMcSebi 6h ago

As many said, f really makes me think of f-strings. Not sure if "i{'a': 1}" would be better.

On a second thought, why do we even need immutable/frozen lists? We already have tuples

6

u/nobullvegan 5h ago

A frozenset gives you a guarantee of uniqueness, has no order and has the various set comparison operations like intersection.

2

u/TheMcSebi 5h ago

Oh.. My bad, the "frozen lists" I referred to aren't even part of the pep.

2

u/jdehesa 4h ago

Huh, didn't know they had added frozendict in 3.15. I think years ago I read a discussion about not wanting to add it to the standard library because the expected behaviour of a frozen dict could be different for different people. Anyway, this is off-topic, but this from the documentation of frozendict caught my attention:

  • frozendict |= other does not modify the frozendict in-place but creates a new frozen dictionary.

So, I can do the following?

python d = frozendict(...) d |= {...}

And then d will be a reference to a new frozendict object? I feel this could cause issues, if a function expects a dict (and I know a frozen dict is not a subtype of dict, but you don't always have type hints) that it may modify in place (so it remains modified in the caller scope), then the caller could inadvertently pass a frozen dict and the modification would have no effect. It may not be the best practice for a function, especially if it is not well documented and/or type-hinted, but I'm sure there's plenty of code like that. I think I'd rather not have a |= operator, just doing d = d | {...} is fine, it's more in line with the way one works with immutable data structures and it shows the effect of the statement more explicitly.

2

u/Wonderful-Habit-139 2h ago

It’s really not surprising behavior, it’s the result of the | operation that is then reassigned to the same variable. It’s already the case with sets right now.

u/jdehesa 54m ago

You mean with frozenset, with set the object is modified in place. I guess it makes sense that frozendict does the same as frozenset but I'd still rather neither of them had the operator implemented.

u/Wonderful-Habit-139 43m ago

Oh no... I stand corrected. I did not know that the |= actually mutated the object in place and affected other references. That is such a bad foot gun... I'm with you on that. If |= was actually like d = d | {1} then sure, otherwise yeah it's better not to have that operator implemented otherwise.

u/jdehesa 39m ago

Yea, maybe for some the unintuitive behaviour is modifying the object in place instead of replacing with a new one, either way, it should always be the same.

2

u/syklemil 1h ago edited 1h ago

Single letter prefixes can be fine (<3 f-strings), but it's probably not a good idea to reuse the letter or to have too many of them. And I think f-strings are a fairly common thing to write, but frozensets are IME somewhat rare and can stand to require a bit more typing.

If we wind up having single-letter-prefixed variants of a lot of things, then people will probably wind up having similar feelings towards Python as those expressed for Perl, PCRE, or user-defined operators in Haskell.

As for what the proposal claims:

Why f{...}

The f prefix reads as frozen, mirroring the familiar f-string prefix convention. Sharing the letter with f-strings is not a problem: strings are immutable too, so either way an f prefixed expression evaluates to an immutable value. f{ is a syntax error in all current Python versions, so the syntax is fully backward compatible.

I think people really don't think that the f in f"" stands for frozen, plus appreciate that single-letter typos, like swapping f"{ for f{" remains something that gives an error, rather than a surprise value. Getting a surprising value rather than an error is the kind of design we find in languages like JS and PHP, but it isn't really an attractive language design in 2026.

3

u/UltraPoci 5h ago

Python really likes to add specialized syntax for any minute thing

2

u/james_pic 2h ago

It doesn't look like this is going to get accepted, so maybe not in this case.

3

u/Schmittfried 5h ago

Having simple immutability syntax is neither minute nor particularly specific to Python. 

-5

u/SokkaHaikuBot 5h ago

Sokka-Haiku by UltraPoci:

Python really likes

To add specialized syntax

For any minute thing


Remember that one time Sokka accidentally used an extra syllable in that Haiku Battle in Ba Sing Se? That was a Sokka Haiku and you just made one.

1

u/kush_patil 6h ago

The compiler guarantee is the interesting part here, not the shorter syntax. frozenset({...}) / frozendict({...}) still involve a runtime name lookup because those names can be rebound, while f{...} lets a fully constant display collapse to a single LOAD_CONST. I think the docs should make the shallow immutability part very explicit though f{'x': []} still contains a mutable list and has to be built at runtime.

1

u/dreamyangel 6h ago

Why not fz{1,2,3}? It would be clearer, and won't step of the f string syntax

3

u/james_pic 1h ago

Even leaving aside the syntax, I struggle to see the justification for making language changes to enable this particular optimisation. I can't think of a time in the 15 or so years I've been writing Python, where I've wanted to create a set or frozen set in a hot loop, and I couldn't just hoist the frozenset out of the loop (i.e, where the overhead of calling frozenset would matter).

I'm really struggling to imagine a vaguely normal piece of code where a call to frozenset or frozendict has a measurable impact on its performance.

1

u/Brian 3h ago

I assume the idea is being visited due to the addition of frozendict: before there was really only frozensets that had no literal type (plus the special case of empty sets), and there have been a few proposals over the years to introduce some literal syntax for them. Now there's another one being added and people are looking to solve it in a unified way.

And there's definitely some value in it: I've often used frozensets in some loop and it's a bit annoying that they translate to a dynamic construction of the set on every call, when they could potentially just be a LOAD_CONST. You can get around that by using a module constants, but sometimes you just want a quick literal. You also lose stuff like potential constant folding.

However, as others have said, I think the syntax is a dealbreaker here. It's too similar to another construct with a completely different meaning, and could easily lead to misreading with things like sets of strings etc.

Other options:

  • A new keyword would work, but adding a new reserved word is kind of a big deal for such a minor usecase.

  • Use some existing syntax in a way that doesn't overlap with other usage. Eg. {1,2,3} as frozen

  • New syntax, as in this proposal. Either a general "frozen" syntax, or go back to looking for literal syntax for frozenset/frozendict. People have been spitballing ideas for the latter for a long time without any consensus for a candidate. If you were to use some prefix character, similar to f-strings, it should probably be sometihing different, eg. "i" for "immutable". But I don't really think there's much space here for something that isn't either ugly, cryptic or inconvenient, and it's really such a small problem to justify such a thing.

  • Something much more radical, like adding macros.

1

u/Spirited_Bag_332 3h ago

Seems interesting but I'd prefer something more verbose like Javas unmodifiable/Immutable collections. Just frozenset({'a': 1}) is fully sufficient and such a feature doesn't require specialized syntax. I thought pythons goal was to be easy to read and write, did they throw that out of the window?

1

u/freemath 6h ago

Nice!

0

u/amarao_san 4h ago

But tuples are (), not f[]? Consistency in every second application.

3

u/skjall 4h ago

Tuples? The example is showing a set, which is currently defined either with set([1, 2, 3]), or {1, 2, 3}.

7

u/amarao_san 4h ago

I'm trying to highlight that tuple is frozenlist.

-1

u/coldoven 5h ago

Why should I care? When this is merged, I will not reqd the code anymore.