PEP 661 (Sentinel Values) has been accepted for release in 3.15!
Posted by M_V_Lipwig@reddit | Python | View on Reddit | 88 comments
After five years of discussion, PEP 661, which adds support for sentinel values, has been accepted and is due for a release in 3.15. The motivation is relatively simple:
MISSING = sentinel('MISSING')
def read_value(default= str | MISSING):
if default is MISSING:
default = "foo"
elif default is None:
default = "bar"
else:
...
The important thing here is a specific way to check if any argument was provided to the function, vs a caller propagating a None to it. The ability to check if an argument was actually provided by the caller was a great feature I liked in FORTRAN, so it's nice that it's made it to python!
Original-Ad-4606@reddit
I’m not sure what all the hate is about. I for one have had many situations where None actually means something and have needed to create my own implementation of a Sentinel value to tell the difference between None and the user simply not providing a value.
Many libraries have had to Sentinels internally… Pandas and Pydantic come to mind.
I welcome this feature!
denehoffman@reddit
Yeah, the hate is goofy, I’ve had so many situations where I’ve needed sentinels (fitting algorithms where None disables a termination method but Default uses the default, for example). The complaints about cluttering the language are silly, it’s a new builtin, a single function!
No_Flounder_1155@reddit
because its an api choice and not a need...
denehoffman@reddit
The use case seems pretty obvious, you let the user disable logging by passing None to some logger constructor.
The real issue is not the “None” turning off logging, it’s that None semantically means “nothing” or “null” so expecting the behavior of passing None to give the default logger doesn’t make any sense. A sentinel value is basically just a replacement for None that has meaning. As I mentioned, a Default sentinel is often very useful when None actually should mean disabling things.
But to the bigger complaint of “it’s an api choice and not a need”: so is the use of “None” in the first place. It’s python, we could force users to make up marker types to represent optional state, but because lots of API choices deem this ugly, we don’t do it. If you didn’t care about making nice APIs and only focused on what is needed to write code, then there is a whole world of Turing-complete ways to do this, they’re just ugly. At the end of the day, we should welcome features that make code more readable and writable, and this does that without any major impact if you don’t want to use it.
No_Flounder_1155@reddit
way to not even understand the point.
denehoffman@reddit
I understand your point just fine, OP’s example isn’t a good one, but that doesn’t mean there aren’t legitimate use cases where the alternative would be more verbose or confusing.
No_Flounder_1155@reddit
ugly is not a fact, but opinion. I asked why this was needed and you respond with "its ugly to not do it this way"....
denehoffman@reddit
Why is list comprehension “needed” when a for loop will do? There are lots of syntax choices in Python which are there to prevent ugly code. The fact is, you could hack in sentinels right now, as many people have been doing (the PEP mentions ways of doing this). I think the only legitimate gripe would be that it’s a built-in and not a member of a module like typing.
No_Flounder_1155@reddit
for comprehensions have optimisations associated with them. Sentinals do not need to be hacked in thst is my point.
Thats one of the big things about this PEP. It seems to come from finding a fix because people are hacking rather than solving the problem. Its fixing the language after seeing bad design patterns, not providing primitives and communicating design decisions.
I'd ask simply when are we introducing a static or final modifier to variables?
denehoffman@reddit
Fair point, list comprehensions aren’t a good example of syntax sugar.
If I understand correctly, you think sentinels are entirely an API choice and not needed if you “properly” design an API. I think your idea of proper here is just as opinionated as my “ugly”. Having an API that would benefit from sentinels isn’t a bad design pattern, it just is a design pattern. We could rewrite any API in ten different ways and different people would prefer each one differently. In the logging example, clearly a Boolean flag in conjunction to the current argument would be better, but what if you have ten such arguments? Now you need ten flags, one for each, a needless complication to the signature.
Another example, suppose you have a dictionary where None is a valid key. How do you distinguish this from a missing key in a get method?
I’m not sure what the point of a static variable in python would be, I’m assuming you’re being sarcastic?
A lot of these problems stem from the fact that Python doesn’t really have proper enums or a type system, it kind of fakes them with a class that acts like an enum and optional type hints. I personally think this shouldn’t be a new built-in, it should be in some module, but I don’t agree that it’s useless clutter.
No_Flounder_1155@reddit
My point is I'm not forcing the language to chabge because of my API design choices...
denehoffman@reddit
None isn’t always an empty string, an empty string can be a distinguished type! You can always come up with an example where None is not semantically the same as “”, 0, False, [], or whatever you choose.
No_Flounder_1155@reddit
I never said None was an empty string...
There are 3 things. Abscence, missing, and a value.
None, a valid empty state and the value cover these. The empty state can be defined by the type itself.
Why would you need to overload None as both a placeholder for absence and default?
BogdanPradatu@reddit
Yeah, I was reading the logging example and wasn't able to see why I would ever do that.
denehoffman@reddit
Sure it’s not the best example, but imagine a field where None has a meaning other than “default” or “not specified”. Matplotlib is a great example of this taken to silly extremes, there are many keywords arguments where None, “none”, and “” all have different behavior, and this ambiguity could easily have been fixed by sentinel values.
wunderspud7575@reddit
I don't disagree with your general point, but leaning on matplotlib, the most pathological API known to mankind, to illustrate your point did raise a chuckle.
denehoffman@reddit
They honestly need to just do a major version bump and completely break the API at this point. But I had to give an example people were familiar with, and it’s a very commonly used pathological API!
memesearches@reddit
Airflow is another
M_V_Lipwig@reddit (OP)
I didn't expect people to get so pissed lol. Btw if you're pissed, go read the 300 comment long PEP discussion which answers most of your questions!
But then again, I suppose there's a reason it took five years to get out of PEP hell! Hopefully null-coalescing operators is the next PEP to get out...
Jason-Ad4032@reddit
I think Python could just implement this as a class method on
Noneand a sentinel.The meaning is very clear, and it would be easy to document. It just needs an appropriate name.
danted002@reddit
null-coalescing operators make sense in some languages, I’m not sure it makes sense in Python. If you want to pluck values from nested objects you have pattern matching which is more readable or just access them directly and wrap the statement with a try … except KeyError …. Better yet use Pydantic to properly parse your input and default the value to None.
Every time I user it it TS there was an if later down the line which would be solved by pattern matching in python
M_V_Lipwig@reddit (OP)
You do realize how much more elegant and readable
a?.b
is over
a.b if a is not None else None
right? Also, "use a third party package for a language feature" is not an appropriate solution.
danted002@reddit
match a: case A(b=type_of_b(b)) do_something_with_(b) case _: # we fucked up so fail gracefully
Is about 10000 times more sound then a?.b where we know fuck all about a or b. Also what would the operator look like in index operations a?[“b”]?
ConcreteExist@reddit
The or operator basically functions as null-coalescing operator already.
M_V_Lipwig@reddit (OP)
Syntatically confusing (are you actually checking for truthiness or "noneiness"?)
Sounds good until your None-ish type has __bool__ disabled...
binaryfireball@reddit
dont get me wrong, im not angry nor do I hate it. its just a meh really
HommeMusical@reddit
"Create your own implementation"? What "implementation" do you need beyond
IAmASquidInSpace@reddit
CHANGE BAD! ME NO LIKE CHANGE! /s
Seriously though, this is a feature I've needed a few times in the past, where
Nonehad a meaning other than "no value provided", and every time I ended up with some hackyobjecttrick and staring longingly at PEP 661. Finally having this in is great!WildCard65@reddit
attrs also has its own sentinel as well
nicholashairs@reddit
Yup I also have a need for this feature 💪💪💪
denehoffman@reddit
Another example:
Adding an argument like
no_timeout: bool’ is semantically confusing and allows for unused state in the signature (connect(timeout=30, no_timeout=True)`).Why can’t you just use
object()for the sentinel? Well for one, it doesn’t serialize, so if you wanted to store some user settings without storing the default (for example, you want to store the fact that the user didn’t set the value but allow the default to change in future versions), you’d have trouble. It also doesn’t play well with type hints, since it just has a type of object.samettinho@reddit
Honestly, I've never needed this before and I can't really see the benefit; maybe something very minute.
denehoffman@reddit
My use case is an optimization library with config object. This object contains arguments which represent algorithm terminator configurations, which are themselves objects. If the user specifies None for one of these terminators, the interpretation is that that terminator isn’t included in the algorithm loop. Omitting the argument would imply the default terminator. A “missing/default” sentinel is an easy choice, and I ended up writing my own code which could be replaced by a single line now.
Could I have rewritten the entire library such that terminators each come with an enabled/disabled Boolean flag? Sure, it would have been even more work, and the underlying code is a Rust library so I’d have to rewrite the bindings for that too. Sentinels just simplify that decision while being invisible to the user, except when they read type hints or documentation, they’ll see “default” instead of “None” and not get confused as to what “None” does.
This extends to any code where “None” has a different semantic meaning than “default” or “missing”.
mpersico@reddit
Named arguments and just don’t pass it. For a language that was supposed to be so simple the amount of stuff that’s being piled on makes it look like C++.
el_extrano@reddit
Pedantic point, but the PRESENT intrinsic to test whether an argument was supplied is only available in Fortran 90 and later, so you can't say it's from "FORTRAN": the all caps spelling is only for the 77 standard and earlier!
M_V_Lipwig@reddit (OP)
You'll have to pry all caps spelling from my cold dead hands!
IcecreamLamp@reddit
I prefer the syntax of
unittest.mock.sentinelover thisaloobhujiyaay@reddit
this is way better than using object() hacks everywhere, readability and intent improve a lot
IcecreamLamp@reddit
Can also use
unittest.mock.sentinellolPh0X@reddit
from an API user, how is
MISSING = object()different fromMISSING = sentinel()?From a contributor point of view, a simple comment above the former saying
This is a sentinel valuedoes basically the same, no?fiddle_n@reddit
The PEP gives a few drawbacks to using object:
Top two affects API users, I would say.
sphen_lee@reddit
Type checkers work better if the sentinel has a type specific to itself, whereas all
objectsare the same typebinaryfireball@reddit
i guess it's nice but tbh i dont feel like the benefits outweigh cluttering the language with even more features. One of the things I like about python is that its fairly idiomatic in the sense that there is one(or a couple) generally accepted way to do things. Pergaps I dont work in the same domain as you but the amount of times ive had to worry about this issue is negligible.
ottawadeveloper@reddit
I've seen a number of libraries use the Ellipsis to do this, eg
The downside is strict typing requires this. to actually be:
And then the type checkers start complaining that
EllipsisTypeisn't a string even though you changed it to a string (which may be a bug in my PyCharm for 3.14 since it's pretty clearly not ever an Ellipsis after the if statement). It doesn't like retyping variables either, so I end up with the uglyreal_arg = 'default' if arg is ... else arg.Anyways, that aside, there's a lot of value for this. If you read the PEP, the current best practice to make one (even in the standard library) is
empty = object(). But that creates weird issues if you copy or pickle it, especially if you useisinstead.Noneis available and works most of the time but not always. And...isn't supposed to be a placeholder even though the datetime module uses it plus you have to use EllipsisType rather than ... in the type hint (unlike None which works in type hints).It would be a lot cleaner to be able to define a sentinel value that works well, can be used in type hints, and can be pickled properly. Nice to see this used.
Effective-Total-2312@reddit
Wouldn't using an Enum and/or a switch case be much better in that scenario ? "None" is not semantic if it intends to mean something, and the exact source of this PEP is exactly that "it may not exactly be None but a missing argument", then why not have an explicit argument ?
I mean, it's in the zen of python.
ottawadeveloper@reddit
An enum with one value seems silly, but I guess you could. You could consider sentinel a one valued enum
M_V_Lipwig@reddit (OP)
Consider reading the PEP...
Effective-Total-2312@reddit
Will definitely do when possible ! I appreciate a lot the work behind the scenes of people in Python, don't get me wrong on my comments
tobsecret@reddit
The whole point is to establish one clear way to implement sentinels. There were many bad ways of doing it, including the bad way you're suggesting.
This is a good addition to the language. If you need a sentinel value, use sentinel. Not every pep has to introduce massive changes.
NuclearFoodie@reddit
Yeah but this just clutters the language, maybe if they took something out to prevent language clutter, maybe classes or something /s
Sorry, my annoyance at the bs clutter argument needed a snarky outlet.
Daishiman@reddit
It doesn´t clutter the language. It removes various mutually incompatible conventions with a single, standardized convention that's trivial to refactor.
NuclearFoodie@reddit
r/whoosh
M_V_Lipwig@reddit (OP)
The PEP addresses this [here](https://peps.python.org/pep-0661/#add-a-single-new-sentinel-value-such-as-missing-or-sentinel)
Dull-Researcher@reddit
Excellent for default immutable values or placeholders for the same, when None has a different meaning.
2ndBrainAI@reddit
This is a genuinely useful addition — the classic workaround was creating a module-level
_MISSING = object()and comparing withis, but that approach has a few rough edges: it doesn't survive pickling, repr shows something unhelpful like<object object at 0x...>, and type checkers struggle to reason about it. PEP 661 sentinels get proper names, predictable repr, and first-class typing support. I'm particularly glad it'll compose cleanly withtyping.overload— differentiating 'no argument provided' fromNonehas always been frustratingly awkward to express in type stubs. Five years of discussion, but it was worth the wait!Orio_n@reddit
Whats wrong with an enum?
tunisia3507@reddit
Because you can't load arbitrary data into an enum (missing OR None OR int).
xfunky@reddit
I think he meant something like this (notice that Guido himself suggested this method)
https://github.com/python/typing/issues/236#issuecomment-227180301
eztab@reddit
was there any problem in just using a class for this? I do have this situation where I need another sentinel since None is taken already and just used a class (that does nothing).
RedSinned@reddit
Ironically I was at PyConDe 2 weeks ago and there was a talk about sentinel values. The talk is not online yet but the slides are: https://pretalx.com/pyconde-pydata-2026/talk/88TTRY/
UltraPoci@reddit
Can't we just use EllipsisType?
Trang0ul@reddit
Why not? If this is the new “official” pattern, the stdlib should lead by example. Otherwise it’s just another optional idiom, not a standard.
assumptionkrebs1990@reddit
Nice solution (I would have likely build a provider (sentinel) wrapper class to handel such cases and then fallen into feature creep.)
Uncle_DirtNap@reddit
This is GREAT NEWS, and haters have to get over it, this is super useful.
Paddy3118@reddit
Please add examples of your own.
HEROgoldmw@reddit
Think about your a custom configuration library. This configured value, is allowed to be None or int.
Now add a feature, where you warn users of unconfigured config values. You can NOT do that using None, as None is a valid value. Thats why where you have an internal use only, default sentinel value named MISSING. Now you know if the config is empty, or set to None. Without sentinels, there's practically no (even remotely easy) solution for the feature.
BossOfTheGame@reddit
I think the best example is a dict-like get method with a default parameter that you can use with keyword args. if you don't specify the default it should error if it is a key error, otherwise it should return the default, and None is a very valid default value. This is exactly the case I wrote ubelt.NoParam for.
Effective-Total-2312@reddit
I don't really understand the benefit. If you use type hints and static type checkers, why would you be doing this kind of thing ? Sounds like a solution to a problem that is only hiding a design issue behind
nosjojo@reddit
This is handy for serializers, where None is a valid object to encode. You want to be able to determine that they actually meant to encode None instead of passing nothing in.
sausix@reddit
Basically every dict.get method when "None" is different from missing.
nicholashairs@reddit
It's very common in libraries where something is acting like a dict and
Noneis a valid value to store so you need to distinguish it from missing values.An example is stdlib contexvars.MISSING https://docs.python.org/3/library/contextvars.html#contextvars.Token.MISSING
Effective-Total-2312@reddit
I kinda don't like it anyway, I think python already has tons of features that would be better than having an argument that could:
- Have a proper value of a specific type
- Be None
- Be Missing
That's too error prone, and also it's too implicit on the meaning of that argument, which goes against the zen of python of prefering always explicit over implicit.
No_Flounder_1155@reddit
People in this thread seem to not understand that by addressing those 3 conditions the need for sentinals disappears.
nicholashairs@reddit
People in this thread understand that there are uses outside of function arguments.
nicholashairs@reddit
So everyone is focused on the argument in the function example.
There are lots of use cases outside of arguments (such as return values or attributes) - such as the example I linked.
sausix@reddit
This kind of complexity is more focussed on developing tools and toolkits. It may look obsolete for end user applications.
dusktreader@reddit
FINALLY.
No_Flounder_1155@reddit
I don't think the logging example is very good. Why should None stop logging from working. Seems really weird.
Marksta@reddit
I'm imagining the use case is you're calling some other code that is going to call log() statements, so you must have a logger object so just not initiating it isn't the solution. If you want the logger totally disabled but you are going to initialize it, then I guess usual options is to set it to something like FATAL where hopefully it goes un-used, pipe it to nothing but maybe this has performance impact that it's still doing something, or hope the implementation has a "you created me to disable me" option. Which sounds like setting log level to None to me.
So for the logger example, the question is would you expect log = Logger(), log.info() to just print nothing? I'd personally assume a no param Logger() object has some sort of defaults that include logging, but a Logger(loglevel=None) and it checking to see that I actually wanted the nothing burger Logger object makes way more sense.
But should a Logger() object with no parameters really just be a disabled logger?
No_Flounder_1155@reddit
you could always just have disable=True. That gets rid of magical side effects.
Marksta@reddit
Love it, I feel like Python has already been so wobbily on wtf None is way more than other languages are with their NULL/VOID etc types. None isn't actually nothing, it has meaning all over the place. Especially with the truthy-falsey stuff. Giving a formal way to discern the implicit None and an explicit None seems like a good move to me.
caprine_chris@reddit
This is amazing.
teerre@reddit
Anything but imponent real enums lol
TMiguelT@reddit
Isn't your example incorrect? You have
level=MISSING|None|strbut I think you meanlevel: MISSING|None|str = MISSINGno? On my first reading I thought this was a bizarre change to how the|operator worked but I now realise that this PEP only addssentinel(which admittedly is handled specially by the type system).M_V_Lipwig@reddit (OP)
Hah you're right! that's what happens when you code without an IDE lol
tobsecret@reddit
I like this. When it comes up, it's nice to have a prebuilt solution that has considered the common edge cases already.
FrickinLazerBeams@reddit
Oh cool look more junk. That's awesome.
BDube_Lensman@reddit
The number of cases where
MISSINGandNoneare able to have legitimate differences in semantic meaning has to be likes of cases, and we're added even more crap to the language to cover those. That's a thumbs down, people should not use this in their code.