Not sure if this is the right place to discuss this, but I found this suggestion for frozen set literals on the mailing list, which seems to have been overlooked:
As a half-baked alternative thought, what about using {{'a', 'b', 'c'}} for the syntax. It’s visually clearer, and still syntactically unambiguous, because a dict can’t have another dict as a key, and a
(frozen)set can’t have a dict inside it. Thinking about possible issues–what if you come across a construct with three opening braces at the start? {{{...
It can’t be a dict within a dict within a dict, because a dict is not hashable, so can’t be a key.
It can’t be a dict within a frozenset, because a dict is not hashable.
It can (and must) be a frozenset within a dict, because a frozenset is hashable, and hence a valid dict key.
Four braces? {{{{...
I think this has to be a frozen set within a frozen set.
More?
The outer one is a dict if an odd number of braces, everything else is a frozen set.
Obviously, the more complex cases get less visually clear, but
they are not common cases
they are still unambigous
And will often be made clearer by
the placement of closing brackets
decent syntax highlighting
We previously discussed this here: Alternative call syntax. So, maybe those posts should be moved here?
Very true. Currently a set literal starts out with BUILD_SET instruction, its value is then loaded onto the stack as a frozenset constant from co_consts and finally an addtional SET_UPDATE instruction has to be performed to convert it to a set. Defining sets that don’t need to be mutated as frozensets can save the overhead of this conversion.
from dis import dis
def f():
s = {1, 2, 3}
dis(f)
outputs:
2 RESUME 0
3 BUILD_SET 0 <= can be skipped with s = {{1, 2, 3}}
LOAD_CONST 1 (frozenset({1, 2, 3}))
SET_UPDATE 1 <= can be skipped with s = {{1, 2, 3}}
STORE_FAST 0 (s)
RETURN_CONST 0 (None)
Yes, the current frozenset defintion is needlessly inefficient in that it first gets loaded as a frozenset constant, then gets converted to a mutable set, and finally gets converted back to a frozenset with a call to frozenset, when the first step would’ve been sufficient.
Such syntax is already legal Python, even though it will always raise a TypeError: you are building a set taking a single element, which is also a set.
For new syntax you will need to come up with something that was not legal Python before.
Why is that a problem? You can’t make {'a', 'b', 'c'} hashable, like you can’t add new methods to string literals. So this would only be useful in code golf, which we don’t care about.
But you are still technically breaking backward compatibility policy, even though the breakage here is largely theoretical. My point is that, if you look at the new syntax introduced in the past 10 years, it was always something that was not legal Python before (PEP 695, PEP 701, PEP 654, PEP 634, PEP 572, PEP 498, PEP 515, PEP 526…).
IMO frozen set literals have benefits, mostly because the compiler would be able to optimize them in some cases, like what is done for tuples (constant folding, lookup optimization). The current ability to shadow the frozenset builtin prevents the compiler from doing that now.
In terms of syntax, I think a prefix would make more sense, like f{2, 3} or else.
Just make the syntax foobar{...} mean foobar(...) except that foobar refers to the built-in. Then the human and the interpreter can both be sure the built-in gets used and the interpreter can optimize frozenset, all, any, etc accordingly. (I’m ~99% joking.)