If subclassing is not something that is entirely out of the world, could we have a PySentinel_CheckExact() alias for PySentinel_Check() (or, rather, the other way round) already in Py3.15, so that users can make the distinction in their code already? Even if both do the same thing for the time being, I don’t see why we should let users pile up code on their side that turns out to be incorrect and needs to be changed if subclassing gets allowed at some point in the future.
I had a minor typing questions while reading the new documentation and the PEP:
The PEP states
If the name passed to
sentinel()does not match the name the object is assigned to, type checkers should emit an error.
but in the example in the documentation, we have this:
class Cls:
PICKLABLE = sentinel("Cls.PICKLABLE")
assert pickle.loads(pickle.dumps(Cls.PICKLABLE)) is Cls.PICKLABLE
which seemingly breaks the above rule. However, it does seem nicer to write "Cls.PICKLABLE" here (and in fact, this seems to be needed for correct pickling), so maybe the typing spec can be formulated to allow this. (Or even require prefixing the class name if a sentinel is defined in a class?)
Sorry, another thought I had:
Is it somehow possible to make sentinels work with match by, e.g., supporting something like the following?
MISSING = sentinel("MISSING")
def next_value(default: int | MISSING = MISSING):
match default:
case MISSING(): # note the parentheses here to avoid a catch-all match
print("missing value")
case val: # this is now `int`
...
"Cls.PICKLABLE" matches the name, the qualified name. This confusion is why I’ve insisted on the exact terminology.
Match support is possible. I’ve had a working prototype of it earlier: PEP 661: Sentinel Values - #317 by HexDecimal (It works with isinstance which means it also works with match)
I don’t feel too strongly about the class name issue, I think we can discuss this when we add sentinels to the typing spec (which I’m working on).
As for match support, this would need some runtime change to how the match statement works. (@HexDecimal’s implementation relies on making each sentinel object a new class, which is not the implementation we ended up with.) I think it’s an interesting idea but too late for Python 3.15.
I posted PEP 661 (sentinels) in the typing spec to discuss incorporating sentinels into the typing spec, and a spec PR Add sentinels (PEP 661) to the spec by JelleZijlstra · Pull Request #2277 · python/typing · GitHub . Let’s move any further discussion of typing there.
I was out of the loop for a while and I just noticed this has been standardised… which is great to see but… I’ve got to be honest… I’m utterly horrified the decision was made to have sentinels evaluate ‘truthy’ in boolean comparisons.
I know its my opinion, and I know it changes nothing since it’s now accepted as written, but I have to say its the first time I’ve ever felt that a PEP got something flat out “wrong”… and I’m a bit sad thinking about how many codebases I work on in future will have a “ProperlyFunctioningSentinel” subclass which patches this “mistake” and has a doc string explaining that allowing sentinels to evaluate true reduces their effectiveness as a safety guard. ![]()
So to your points @StelarDream up here PEP 661: Sentinel Values - #340 by StelarDream and your reply @scoder PEP 661: Sentinel Values - #343 by scoder … Can we try and not ship this without support for subclassing since it wasn’t explictly restricted in the PEP? Because I’m going to be want to use code like this from the moment sentinels ship.
from typing import Never
class UniqueSentinel(sentinel):
def __bool__(self) -> Never:
raise NotImplementedError("This sentinel can not be safely coerced to a boolean value")
Hi Sam,
It’s unfortunate to get your input on this so late in the process. This was discussed in detail over several years, and honestly I agonized over truthiness a lot.
It would be much, much more useful to hear your arguments on the matter, even after the fact, than just a statement that that is “flat out wrong”.
EDIT: Sam laid out his arguments on this matter in detail much earlier in this thread, I simply forgot ![]()
FWIW I’d consider adding the ability to set the truthiness, including to a value which would cause __bool__ to raise, much more reasonable than adding the ability to subclass.
Their arguments were presented:
The discussion has gone on for another 120 posts, so it’s easy to lose them.
Wow, thanks! It’s been so long, and I’ve been away from this for a good while, I forgot ![]()
Those are certainly very well thought out and written arguments. I appreciate them a lot, and considered them deeply while working on this PEP.
The counter-arguments I mainly considered were:
- A major focus was to be useful in the stdlib, and a careful review showed that all such sentinels in the stdlib were truthy.
- It is extremely unusual for Python objects to raise an exception when evaluated in a boolean context. I was very worried about the many esoteric edge cases that would come up when this would be rolled out to many millions of users and broke things in myriad unexpected ways. So deciding whether to have
__bool__(possibly) raise an exception was an especially delicate decision.
In the end, in part to avoid endless “analysis paralysis”, I opted to keep this as simple as possible to begin with. In the future we can relatively easily add something like the ability to set truthiness, but that is the kind of thing we could practically never change in hindsight.
In the meantime, the existing 3rd-party sentinel libraries are fine! Having this in the stddlib doesn’t mean everyone has to switch to them. We can consider down the line if we want to make the stddlib sentinels more generally useful to address additional use cases.
Hmmm, Built-in Functions — Python 3.15.0a8 documentation says “Added in version 3.15.0a8” but my instance of that version still claims NameError: name ‘sentinel’ is not defined
It will be in the first beta (to be released tomorrow). We might be reporting a slightly wrong version in the docs.
And note the magic final word:
Added in version 3.15.0a8 (unreleased).
Did you find any code that evaluated the __bool__ of these stdlib sentinels?
If they’re not evaluated, then it doesn’t matter that they’re truthy.
I asked this earlier PEP 661: Sentinel Values - #301 by jamestwebber
And got the response:
So the latest reasoning seems shaky to me.
I think the most definitive reason to treat sentinels as truthy is that the SC explicitly said that was fine by them:
Rehashing that decision now seems to have little benefit to anyone.
Going out on a bit of a limb here, but perhaps the discussion could be useful to the future version of python 3.16? Perhaps extra discussion could be informative to the future?
Well, it would be bad to break backwards-compatibility. So if sentinels are truthy in 3.15, they should be truthy forever. (Though I’m not sure whether you’re maybe referring to some other discussion topic.)
The discussion is whether there should be a way to instantiate a ‘falsy’ Sentinal.
Personally I’ll be using
XXX = sentinal("XXX")
XXX.__bool__ = lambda: False
if that works (I haven’t been able to test the python 3.15 implementation), until any such time at which better support is added.