Should we specify in the language reference that the empty tuple is a singleton?

I think it’s pretty commonly known that the empty tuple is a singleton in Python:

>>> x = ()
>>> y = ()
>>> x is y
True

I can find lots of references to this online (not really “reputable sources”, just examples of it being common knowledge):

And I can find lots of examples of code making use of this property in the wild:

And also in some parts of the standard library: https://github.com/python/cpython/blob/fcef3fc9a593e2aa868d23cf2d91c57d8bf60ac6/Modules/itertoolsmodule.c#L2278-L2281

But to my surprise, the language reference says that this is an implementation detail:

An empty pair of parentheses yields an empty tuple object. Since tuples are immutable, the same rules as for literals apply (i.e., two occurrences of the empty tuple may or may not yield the same object).

Is it maybe time to guarantee this property as part of the language spec? I can’t see us ever realistically changing this, as I think it would break too much code.

I suppose adding it to the language spec would mean that other implementations would also have to make sure that () was implemented as a singleton. But I would guess that they all already do, because it’s used as a singleton so much in the wild. From experimenting locally, at least PyPy already does.

If it helps for some additional context from alternate implementations (picked entirely because I already had them installed):

GraalPy doesn’t appear to do this.

Python 3.11.7 (Mon Oct 07 10:19:29 UTC 2024)
[Graal, Oracle GraalVM, Java 24 (amd64)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> x = ()
>>> y = ()
>>> x is y
False

Micropython does.

MicroPython v1.21.0 on 2024-04-22; linux [GCC 11.4.0] version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> x = ()
>>> y = ()
>>> x is y
True
1 Like

It should remain an implementation detail IMHO. There is no reason I can think of to write x is ().

14 Likes

Indeed, Python 3.14 (and maybe 3.13?) considers the latter worthy of a SyntaxWarning (as I found out while checking whether the singleton check was handled inside or outside PyTuple_New):

>>> import ctypes
>>> ctypes.pythonapi.PyTuple_New.restype = ctypes.py_object
>>> ctypes.pythonapi.PyTuple_New(0)
()
>>> ctypes.pythonapi.PyTuple_New(0) is ()
<python-input-4>:1: SyntaxWarning: "is" with 'tuple' literal. Did you mean "=="?
  ctypes.pythonapi.PyTuple_New(0) is ()
True
5 Likes

Yes, 3.13 as well. And 3.12.

2 Likes

Yes, that’s a very good point, and suggests that those who are using () as a sentinel value should probably stop doing so. That’s pretty persuasive that we should therefore leave this as an implementation detail.

4 Likes

object() is a better sentinel in many cases anyway

3 Likes

I prefer to use ['sentinel'] as it has more distinguisive repr than <object object at 0xabcdabcd12345670>.

FWIW, I often use it like so:

missing = object()
value = somedict.get('optional', missing)
if value is missing:
    # None could be a valid value
1 Like

3.9 too, and I think it was added here, so should be on 3.8+

Sentinels have been the subject of a lot of discussion. PEP 661 tried
to codify a sentinel type that solves some of the issues of
identification that have been mentioned already. Didn’t seem to get
enough traction to move all the way through the process, but it’s still
a place where a lot of thoughts around the situation were recorded, may
be worth a look.

Actually, it was submitted to the Steering Council for consideration this week :slight_smile: PEP 661 -- Sentinel Values · Issue #258 · python/steering-council · GitHub