(partly inspired by the discussion in this thread)
This seems useful to be able to do:
UNKNOWN = sentinel("UNKNOWN")
UNKNOWN.__doc__ = "Sentinel to represent values that cannot be calculated"
so that, e.g., sphinx can pick up the docstring.
However, currently this happens:
Python 3.15.0rc2 (main, Sep 1 2026, 14:05:48) [Clang 22.1.3 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> UNKNOWN = sentinel("UNKNOWN")
>>> UNKNOWN.__doc__ = "Sentinel to represent values that cannot be calculated"
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
UNKNOWN.__doc__ = "Sentinel to represent values that cannot be calculated"
^^^^^^^^^^^^^^^
AttributeError: 'sentinel' object attribute '__doc__' is read-only
>>> UNKNOWN.__doc__
'Create a unique sentinel object with the given name.'
This might just be a limitation of the implementation, in which case there’s nothing we can do, but if it is in fact possible to make __doc__ writable, I think that would be a good idea.
As a Sphinx maintainer it would make sense indeed. In the meantime, you should be able to make it work via inline doc syntax using #: comments above IIRC.
The reference implementation has a snag, because the class defines __slots__, the docstring on the class must be dropped, such that a slot for __doc__ can be added to the instance. This is something I’ve hit on a few occasions when creating descriptors that define __slots__. Would certainly be nice to allow a class to have a docstring and let a slotted instance define a __doc__.
The other technical hiccup here, I am not sure how __doc__ is inferred for sentinals. I know that descriptors (specifically, anything that defines __get__), have some special casing where help() looks at the instance __doc__, or at least it appears to operate that way.
Sentinels do not currently support per-instance docstrings; there’s no place to store them on the object. The __doc__ attribute referenced in the error message above is the doc for the sentinel class.
Adding per-instance docstrings would be possible, but it would be a new feature and it’s probably too late for Python 3.15.
After thinking about it more, I think a doc= argument is preferable because it can also be easily picked up by static tools, like IDE hover text, and there’s precedence with, e.g., x = property(getx, doc="I'm the 'x' property.").
Some remaining questions:
I think the default value should be None rather than the sentinel class-docstring, so this would be a very slightly breaking change.
Should __doc__ then be read-only? I’m slightly favoring yes here.
I suppose the docstring should also be settable from C. If __doc__ were writable, it could just be set directly, but if it’s read-only, then I think that means adding a new PySentinel_NewWithDoc next to PySentinel_New.
Today I ran into a scenario in line that’s somewhat in the extension of this thread: I would like my DISTANT_PAST sentinel to satisfy DISTANT_PAST < t so that I can replace if instance._valid_until is DISTANT_PAST or instance_valid_until < t with a simpler (and more performant) if instance._valid_until < t.
(I know the particular code I’m working on right now will probably settle for using DISTANT_PAST = datetime(2000, 1, 1, tzinfo=AMS) or something like that, so I’m not requesting this to make the current project easier. But I do expect that things like this will keep popping up.)
so that I can type hint it as datetime | DISTANT_PAST
so that the representation is DISTANT_PAST, which can be easily grepped if it turns up in a log or during debugging, and not datetime(1999, 12, 31, 23, 0, 0, tzinfo=("-01:00")) or whatever the the repr of this constant is going to turn into (which I, as you can tell, cannot predict nearly as easily), and which can be absolute hell to find the origin of.
because DISTANT_PAST carries the role of -math.inf in floats, it’s not really supposed to be part of the mathematics.
so that during serialisation, we have to make a decision about DISTANT_PAST (and we’d probably translate it to null or to a missing key) whereas if it ‘just’ a CONSTANT datetime, it’s naturally going to turn into 1999-12-31T22:00:00Z or something like that, and it’ll be far less natural to special-case it.
what happens to isoformat? do I have a .seconds attribute? a tzinfo? So many questions, and so many subtle ways to go wrong.
it becomes type-hinted as datetime instead of datetime | DISTANT_PAST, so people will just (quite reasonably) try to isoformat it, without realising that the attribute might be some special subclass of datetime, and the type-checker won’t be able to complain.
I have no grasp of all the things that I would inherit. The type checker exists to help us. By using inheritance to hide the special cases, you’re sabotaging the type-checker.
FastAPI/Pydantic have isinstance checks for datetime, and the behaviour FastAPI deploy as soon as they detect a datetime instance is problematic, even just with Annotated datetime. Using a datetime subclass introduces all kinds of new cursed ways for this to go wrong.
I can define the constant easily enough without inheriting.
But that’s inferior to using the sentinal framework in a few ways, to the point that ‘just’ using 1 jan 2000 00:00 as a magic datetime is probably preferable.
One big thing sentinel gives us is a single name, which refers both to the class and to the instance. That’s pretty big in my opinion. Compare
from utils import DistantPast, DISTANT_PAST
from datetime import datetime
class C:
t: datetime | DistantPast = DISTANT_PAST
with
from utils import DISTANT_PAST
from datetime import datetime
class C:
t: datetime | DISTANT_PAST = DISTANT_PAST
I think the latter is significantly more readable.
Sentinel also has a collection of minor features like pickle-friendliness that I don’t really care about that much on a case-by-case basis, but all taken together they do become significant.
I can empathize with you, I have a sentinels I created that overload rich comparisons and a few others but at the same time, implementing them with __new__ on the class is trivial too. Not sure we’ll get support for doing stuff like this.
Quick edit: The biggest downside is not getting type checker support, so there is that.