This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Title: Generate frozenset constants when explicitly appropriate
Type: enhancement Stage: test needed
Components: Interpreter Core Versions: Python 3.11
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Dennis Sweeney, serhiy.storchaka, steven.daprano, terry.reedy
Priority: normal Keywords:

Created on 2022-01-16 03:11 by terry.reedy, last changed 2022-04-11 14:59 by admin.

Messages (10)
msg410666 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2022-01-16 03:11
The CPython compiler is capable of making frozenset constants without being explicitly asked to.  Exactly how it does so is, of course, 'hidden' from python code.  With current main:
>>> dis('{1,2,3}')
  1           0 BUILD_SET                0
              2 LOAD_CONST               0 (frozenset({1, 2, 3}))
              4 SET_UPDATE               1
              6 RETURN_VALUE

Suppose one wants actually wants a frozenset, not a mutable set.  'frozenset({1,2,3})' is compiled as the above followed by a frozenset call -- making an unneeded double conversion to get what already exists. 
To avoid the intermediate set, one can use a constant tuple instead.

>>> dis('frozenset((1,2,3))')
  1           0 LOAD_NAME                0 (frozenset)
              2 LOAD_CONST               0 ((1, 2, 3))
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

Even nicer would be

  1           0 (frozenset({1, 2, 3}))
              2 RETURN_VALUE

'set((1,2,3))' is compiled the same as 'frozenset((1,2,3)), but frozenset does not having the option is using a more efficient display form.  I cannot think of any reason to not call frozenset during compile time when the iterable is a constant tuple.

Serhiy, I not sure how this relates to your issue 33318 and the discussion therein about stages, but it does relate to your interest in compile time constants.
msg410668 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2022-01-16 03:24
The difficulty is that frozenset may have been shadowed, or builtins monkey-patched, so we cannot know what frozenset({1, 2, 3}) will return until runtime.

Should we re-consider a frozenset display literal?

    f{1, 2, 3}

works for me.
msg410673 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2022-01-16 04:40
Sigh.  You are right.  I will close this tomorrow.

This also means that 'set()' is not guaranteed to return an empty built-in set. I did think of this workaround for that:
>>> (empty:={None}).clear()
>>> empty
Go ahead and propose something on python-ideas if you want, pointing out that only displays (and comprehensions) are guaranteed to result in a builtin.
msg410674 - (view) Author: Dennis Sweeney (Dennis Sweeney) * (Python committer) Date: 2022-01-16 05:27
There's also the hacky expression {*()} to get an empty set
msg410683 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2022-01-16 08:41
Proposed on Python-Ideas.
msg410684 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2022-01-16 08:50
> To avoid the intermediate set, [...]

It's not quite as bad as that: there _is_ no intermediate set (or if you prefer, the intermediate set is the same object as the final set), since the frozenset call returns its argument unchanged if it's already of exact type frozenset:

>>> x = frozenset({1, 2, 3})
>>> y = frozenset(x)
>>> y is x

Relevant source:
msg410688 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2022-01-16 09:51
That's not always the case though. 

>>> def f():
...     return frozenset({1, 2, 3})
>>> a = f.__code__.co_consts[1]
>>> a
frozenset({1, 2, 3})
>>> b = f()
>>> assert a == b
>>> a is b

Also see the disassembly I posted on Python-Ideas.
msg410692 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2022-01-16 10:07
> That's not always the case though.

Sorry, yes - I see. We're not creating a frozenset from a frozenset - we're creating a frozenset from a regular set from a frozenset. :-(

Sorry for the noise.
msg410706 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2022-01-16 18:10
Rejected by the reality of Python's dynamism, which I overall appreciate ;-).
msg410751 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2022-01-17 09:31
As Steven have noted the compiler-time optimization is not applicable here because name frozenset is resolved at run-time.

In these cases where a set of constants can be replaced with a frozenset of constants (in "x in {1,2,3}" and in "for x in {1,2,3}") the compiler does it.

And I don't think there is an issue which is worth changing the language. Creating a frozenset of constants is pretty rare, and it is even more rare in tight loops. The most common cases (which are pretty rare anyway) are already covered.
Date User Action Args
2022-04-11 14:59:54adminsetgithub: 90551
2022-01-17 11:03:45mark.dickinsonsetnosy: - mark.dickinson
2022-01-17 09:31:36serhiy.storchakasetmessages: + msg410751
2022-01-16 18:10:43terry.reedysetmessages: + msg410706
2022-01-16 10:07:39mark.dickinsonsetmessages: + msg410692
2022-01-16 09:51:14steven.dapranosetmessages: + msg410688
2022-01-16 08:50:08mark.dickinsonsetnosy: + mark.dickinson
messages: + msg410684
2022-01-16 08:41:18steven.dapranosetmessages: + msg410683
2022-01-16 05:27:57Dennis Sweeneysetnosy: + Dennis Sweeney
messages: + msg410674
2022-01-16 04:40:13terry.reedysetmessages: + msg410673
2022-01-16 03:24:40steven.dapranosetnosy: + steven.daprano
messages: + msg410668
2022-01-16 03:11:25terry.reedycreate