# PEP 661: Sentinel Values

**URL:** <https://discuss.python.org/t/pep-661-sentinel-values/9126>\
**Category:** PEPs\
**Created:** [June 6, 2021, 8:07am UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126 "2021-06-06T08:07:06Z")\
**Posts on this page:** 20\
**Page:** 12

<div class="post-metadata">

**Author:** ![ncoghlan](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ncoghlan/32/14266_2.png) [@ncoghlan](https://discuss.python.org/u/ncoghlan)\
**Post date:** [January 30, 2025, 11:52am UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/223 "2025-01-30T11:52:02Z")

</div>

> [@NeilGirdhar](#):
>
> Another advantage to raising is that if you ever change your mind, it’s easy to go from raising to returning true or false. It’s hard to go the other way without breaking code.

I think this is the key point here.

Defining ` __bool__ ` as raising an exception on sentinels keeps _all_ of the design options open for future consideration:

- never support truth checking on sentinels
- be false like `None`
- be true like `object()`, `NotImplemented`, or `...`
- allow users of the library to define the desired truth value when defining the sentinel

It does mean choosing the bikeshed colour for _which_ exception to raise (`TypeError` was the first one that came to mind for me, but then I thought `NotImplementedError` could be a good option, since that would still be valid in a future where this behaviour is configurable)

Edit: I’ll note that `any(...)` and `all(...)` will trigger the exception, which would potentially cause problems for sentinels-as-return-values use cases. However, even for those use cases, truth value checking only works if the sentinel’s truth value is reliably different from the truth values of all other possible results, and the simplest way to be 100% certain of that is to use an identity check.

---

<div class="post-metadata">

**Author:** ![bwoodsend](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bwoodsend/32/4027_2.png) [@bwoodsend](https://discuss.python.org/u/bwoodsend)\
**Post date:** [January 30, 2025, 1:38pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/224 "2025-01-30T13:38:01Z")

</div>

> [@ImogenBits](#):
>
> My initial thought process was that there’s too many parts of python where something will implicitly call ` __bool__ ` for it to reasonably raise an exception. But thinking about it more, I’m not sure how true that is.

For what it’s worth, numpy arrays are (mostly) unboolable and we’ve been happily tossing those around into Python’s various containers for years \[1\] so I’d say that numpy has shown this (understandable) caution to be unwarranted.

* * *

1. the only times I ever run afoul of its unboolable-isms was when not complaining would have led to a logic bug

---

<div class="post-metadata">

**Author:** ![ImogenBits](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/imogenbits/32/29566_2.png) [@ImogenBits](https://discuss.python.org/u/ImogenBits)\
**Post date:** [January 30, 2025, 1:55pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/225 "2025-01-30T13:55:00Z")

</div>

Oh yeah, you’re totally right. In that case I’ve fully changed my mind and also side with the always raising option.

---

<div class="post-metadata">

**Author:** ![kapinga](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/kapinga/32/24087_2.png) [@kapinga](https://discuss.python.org/u/kapinga)\
**Post date:** [January 30, 2025, 4:22pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/226 "2025-01-30T16:22:07Z")

</div>

> [@ImogenBits](#):
>
> But what about something where you have a bunch of computations that can return sentinel values and you throw them into some collection? I can’t really think of a reason why the collection _should_ check the truth value of things in it, but I also can’t really guarantee that it doesn’t.

My primary use of Python is for data analysis, so I regularly work with numpy arrays and/or DataFrames, which raise if ` __bool__ ` is called. In my experience, the only times I run into that exception is if I’ve mis-coded something and accidentally provided an array when I meant to provide a scalar value. In those instances, I am glad to have the raise occur, so I can fix the code (rather than have all arrays silently evaluate as True).

I’ve never encountered a time where a std lib collection _implicitly_ checked the truthiness of such structures (although I can’t say that I’ve knowingly used all of them).

> [@ncoghlan](#):
>
> Edit: I’ll note that `any(...)` and `all(...)` will trigger the exception, which would potentially cause problems for sentinels-as-return-values use cases. However, even for those use cases, truth value checking only works if the sentinel’s truth value is reliably different from the truth values of all other possible results, and the simplest way to be 100% certain of that is to use an identity check.

I tend to agree, although I still am struggling to think of a way where it would be obviously beneficial for a sentinel to be “Truthy.” The main argument in favor of this seems to just be “this should replace using `object()` for sentinels, and `object()` is truthy.” That’s fair enough, but we’re proposing a new API - this is _the_ chance to change that behavior.

My first vote (which likely doesn’t mean much) would be to have ` __bool__ ` raise, as this allows users switching to this API to catch instances where their sentinel’s truthiness was evaluated (perhaps by mistake) and force a change to the surrounding code.

If the SC/community feels that it’s not appropriate to have a type that raises on ` __bool__ ` in the std lib, then giving the user a choice is the next best option. Drop in replacements for `object()` would then use a Truthy sentinel, while newly created “None alternatives” (e.g. `MISSING`) would likely favor using a Falsey sentinel.

---

<div class="post-metadata">

**Author:** ![bwoodsend](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bwoodsend/32/4027_2.png) [@bwoodsend](https://discuss.python.org/u/bwoodsend)\
**Post date:** [January 30, 2025, 7:33pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/227 "2025-01-30T19:33:28Z")

</div>

> [@bwoodsend](#):
>
> For what it’s worth, numpy arrays are (mostly) unboolable and we’ve been happily tossing those around into Python’s various containers for years [[1]](#footnote-223928-1) so I’d say that numpy has shown this (understandable) caution to be unwarranted.

… although it occurs to me that, since numpy arrays are also unhashable, they’ve never been exposed to being in a set or a dict’s keys or such like. But unless someone can think of a scenario where some hash-based container would index objects internally factoring in their truthiness, I can’t see this being significant?

---

<div class="post-metadata">

**Author:** ![Monarch](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/monarch/32/18530_2.png) [@Monarch](https://discuss.python.org/u/Monarch)\
**Post date:** [January 30, 2025, 9:14pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/228 "2025-01-30T21:14:01Z")

</div>

The more I think about it, the more I prefer raising an exception in ` __bool__ `. It’s the more correct behaviour with the only downside being it won’t replace as many of the existing `object()` sentinels. I think a newer API being more correct by default should take precedence over compatibility with existing solution. It might even help uncover some bugs after people replace their `objects()` with the new sentinels and get an appropriate error where a check was wrongly relying on ` __bool__ ` instead of `is`.

---

<div class="post-metadata">

**Author:** ![oscarbenjamin](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/oscarbenjamin/32/1209_2.png) [@oscarbenjamin](https://discuss.python.org/u/oscarbenjamin)\
**Post date:** [January 30, 2025, 9:50pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/229 "2025-01-30T21:50:01Z")

</div>

> [@bwoodsend](#):
>
> … although it occurs to me that, since numpy arrays are also unhashable, they’ve never been exposed to being in a set or a dict’s keys or such like. But unless someone can think of a scenario where some hash-based container would index objects internally factoring in their truthiness, I can’t see this being significant?

Dicts and sets use ` __hash__ ` and ` __eq__ `. They don’t use ` __bool__ `.

If you want a real life example of a type that is hashable and whose ` __bool__ ` raises an exception then SymPy’s `Boolean` type is there:

```python
In [4]: from sympy.abc import x, y

In [5]: e = x + y > 2

In [6]: e
Out[6]: x + y > 2

In [7]: bool(e)
...
TypeError: cannot determine truth value of Relational: x + y > 2

```

This raises an exception because it could true or false depending on the values of the symbols `x` and `y`.

There is no problem putting these expressions into sets or dicts:

```python
In [8]: {x > 1, y > 3}
Out[8]: {x > 1, y > 3}

In [9]: (x > 1) in {x > 1, y > 3}
Out[9]: True

```

---

<div class="post-metadata">

**Author:** ![techdragon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/techdragon/32/1338_2.png) [@techdragon](https://discuss.python.org/u/techdragon)\
**Post date:** [February 6, 2025, 3:20pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/230 "2025-02-06T15:20:11Z")

</div>

I think this has an extremely easy answer… Since Sentinels will be new to the standard library, give them a new standard library exception to raise… my personal suggestion would be raising something like ‘SentinelComparisonError’ which is either a subclass of ValueError, or a subclass of SentinelError which could potentially be a subclass of ValueError.

Since existing code using sentinels will likely (in my experience) be using existing custom exceptions, and code adopting sentinels once they are in the standard library will have no established exception yet, it seems most straightforward to define a new exception to go with the new functionality. The bikesheding here feels like it should be only be if “ValueError” is appropriate as a parent class due to potential interaction with existing code.

---

<div class="post-metadata">

**Author:** ![techdragon](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/techdragon/32/1338_2.png) [@techdragon](https://discuss.python.org/u/techdragon)\
**Post date:** [February 6, 2025, 3:41pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/231 "2025-02-06T15:41:56Z")

</div>

This is the core of my argument for needing some ability to define a sentinel that errors on Boolean comparisons.

There is a significant body of python code that functions by passing kwargs dicts, and while it’s easy enough for a developer to introspect the call chains and decide if None, object(), etc is best… the problem is that they aren’t in control of third party code, a point update could restructure some comparisons assuming nothing is different and introduce inadvertent changes to what would happen with None/False/True comparing values…

Since this will be new to the standard library, let’s add the minimal extra complexity to allow choosing or make the explicit decision to raise the exception. The effort to keep it “minimal” feels like trying to decide if a sheet of paper is too thick… technically a valid decision… and understandable given the significance of trying to get new code into the standard library… but it feels like a somewhat foolhardy line of thinking given how with or without the options, the code will still be extremely minimal and have limited impact on existing code compared to many successful new additions to core python in recent years.

---

<div class="post-metadata">

**Author:** ![Lucas\_Malor](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/lucas_malor/32/24845_2.png) [@Lucas\_Malor](https://discuss.python.org/u/Lucas_Malor)\
**Post date:** [February 6, 2025, 7:45pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/232 "2025-02-06T19:45:05Z")

</div>

My impression is that all the fear about misuse of sentinels is highly overrated. AFAIK sentinels are used by seasoned programmers, that knows very well they have to use `is`. Someone can search on real code how much time sentinels are misused?

That said, I see no real use case for doing `if _sentinel`, so for me new sentinels can also cause a segfault on ` __bool__ ` 😃 Just please add a meaningful error message if you really want to have an exception.

---

<div class="post-metadata">

**Author:** ![jpgoldberg](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jpgoldberg/32/7082_2.png) [@jpgoldberg](https://discuss.python.org/u/jpgoldberg)\
**Post date:** [February 20, 2025, 4:38pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/233 "2025-02-20T16:38:59Z")

</div>

I realize that I am four years late for this discussion, but only learned the term “sentinel” and about PEP 661 a few days ago. I wish to voice my support for RFC 661 and don’t know where else to do so.

I have found myself using (or wanting to use) `None` in four different ways, three of them are sentinels.

1. `None` as none. That is a non-sentinel value. This is usually for specifying thresholds or limits for things like iteration cutoffs or timeouts, and such. These have default values that are not `None`, but for which `None` is a sensible value.
2. `None` for Not Given, as discussed above and in the PEP
3. `None` for Missing, as discussed above and in the PEP
4. `None` for Not Yet Computed. I probably should just be using [cached\_property](https://docs.python.org/3/library/functools.html#functools.cached_property) instead, but I didn’t know about that at the time.

In each case if I find some conflicting use of `None` within some context, I can work around it easily enough, but adopting PEP 661 would mean that I (and others) wouldn’t have to find such work-arounds. More importantly it would allow me (and others) to actually have our code say what we mean.

---

<div class="post-metadata">

**Author:** ![barry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barry/32/42_2.png) [@barry](https://discuss.python.org/u/barry)\
**Post date:** [February 27, 2025, 10:51pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/234 "2025-02-27T22:51:42Z")

</div>

The Python Steering Council recently discussed [PEP 661](https://peps.python.org/pep-0661/), Sentinel Values. This post is on behalf of the SC.

We’d like to thank Tal for writing this PEP and leading a fruitful discussion on DPO. Although we are not yet pronouncing on the PEP, the SC is generally supportive of the idea of adding official sentinel values to Python.

As the use of `object()` for unique sentinels shows, the motivation for sentinels is clear, and we agree with the rationale provided in the PEP. We think that sentinels should be at least as easy to use as `object()`, while providing clear benefits over that simple and existing idiom, and we agree the implementation should be as simple as possible to achieve these goals.

Here is some additional feedback on the PEP. We’re not implying that acceptance of the PEP is conditional on accepting our recommendations, but we want to give you a clear idea of what we’re thinking, and we’d like to encourage you to take advantage of [Steering Council office hours](https://github.com/python/steering-council?tab=readme-ov-file) if you’d like to discuss our feedback in person.

The PEP proposes that sentinels within the same module are unique, and implements a global registry to ensure this. We think that this is “magical” behavior that isn’t supported strongly enough by the rationale and motivations in the PEP. Why is this implicit behavior necessary? Wouldn’t defining a module global sentinel explicitly and then referring to it by variable name be sufficient? We suggest that removing this behavior would keep the implementation simpler while still achieving the goals of the PEP.

Along those same lines, the PEP proposes to auto-discover the module that the sentinel is defined in, while still providing an optional `module_name` argument to the constructor for cases where the auto-discovery can’t or doesn’t work. While this parallels the design of `Enum` and `namedtuple`, we aren’t convinced that this is useful for sentinels, especially if the global registry feature is removed as suggested above. We think a single `name` argument, which would be used directly in the `repr`, should be sufficient to achieve the goals of the PEP. If the user wanted to include the module name in that argument, it’s easy enough to do so explicitly.

We think it’s fine for sentinels to evaluate as truthy in boolean contexts.

The PEP proposes a new module called `sentinellib` which will contain a `Sentinel` class. We note however that in the [section titled “Typing”](https://peps.python.org/pep-0661/#typing), the examples use a module called `sentinels`. In either case, we think that to improve adoption by making sentinels as easy to use as the current `object()` idiom, a builtin type / callable called `sentinel` should be provided. With a simplified implementation, it may not even be necessary to implement sentinels as Python code living in a new standard library module (e.g. it could be implemented in C).

Thanks again Tal, and we’re looking forward to further discussions.

---

<div class="post-metadata">

**Author:** ![taleinat](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/taleinat/32/92_2.png) [@taleinat](https://discuss.python.org/u/taleinat)\
**Post date:** [March 1, 2025, 10:01am UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/235 "2025-03-01T10:01:49Z")

</div>

Regarding the registry, I took some time to recall how I arrived at that design after many iterations, and realized that there is one main reason: Sentinel objects are often not defined on the module top-level, but I’d still like them to “just work” in those cases.

Searching online Python code for `object()`, e.g. on GitHub, reveals that a significant portion of its use is inline in function/method signatures, and another significant portion is in code inside functions/methods. In the latter case especially, it is often not easily possible to move the definition to the module top-level. Sometimes, these are intentionally intended to be different in every function call / loop iteration.

To illustrate, here’s an example from [the stddlib heapq module](https://github.com/python/cpython/blob/75f38af7810af1c3ca567d6224a975f85aef970f/Lib/heapq.py#L472):

```python
def nsmallest(n, iterable, key=None):
    """Find the n smallest elements in a dataset.

    Equivalent to: sorted(iterable, key=key)[:n]
    """

    # Short-cut for n==1 is to use min()
    if n == 1:
        it = iter(iterable)
        sentinel = object()
        result = min(it, default=sentinel, key=key)
        return [] if result is sentinel else [result]

    ...

```

I think that if we add a sentinel to the stddlib, and even moreso If we add a builtin, this will be seen as encouragement to use it in such cases. I feel strongly that it should be something that can be used simply and with confidence, something that will “just work” without weird caveats and edge-cases. The registry is the best approach I’ve found to achieve that.

In my mind, the suggestion to rely on being defined in a module top-level for copying/unpickling to work as expected, means unnecessarily leaving room for things break subtly.

(Side-note: a real implementation with a registry would likely use weakrefs to allow such objects to be garbage-collected.)

---

<div class="post-metadata">

**Author:** ![Jelle](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jelle/32/1049_2.png) [@Jelle](https://discuss.python.org/u/Jelle)\
**Post date:** [March 1, 2025, 3:48pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/236 "2025-03-01T15:48:54Z")

</div>

Is it likely that you need to both declare the sentinel in a local scope, and be able to pickle it?

As an analogy, function objects can also be created in local scopes, but if so they cannot be pickled successfully. I think it would make sense for sentinels to work the same way.

---

<div class="post-metadata">

**Author:** ![barry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barry/32/42_2.png) [@barry](https://discuss.python.org/u/barry)\
**Post date:** [March 1, 2025, 5:05pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/237 "2025-03-01T17:05:34Z")

</div>

Speaking _not_ from the SC, that’s my question as well. Rewriting the example:

```python
def nsmallest(n, iterable, key=None):
    """Find the n smallest elements in a dataset.

    Equivalent to: sorted(iterable, key=key)[:n]
    """
    # Short-cut for n==1 is to use min()
    if n == 1:
        it = iter(iterable)
        missing = sentinel('nsmallest')
        result = min(it, default=missing, key=key)
        return [] if result is missing else [result]

```

In this case, `missing` doesn’t need to be pickled, right? What am I missing\[1\]?

* * *

1. pun intended! 😄

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [March 1, 2025, 5:39pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/238 "2025-03-01T17:39:51Z")

</div>

Also, presumably a registry could be added at a later date if it turned out to be needed, couldn’t it?

---

<div class="post-metadata">

**Author:** ![avylove](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/avylove/32/12544_2.png) [@avylove](https://discuss.python.org/u/avylove)\
**Post date:** [March 2, 2025, 10:19pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/239 "2025-03-02T22:19:57Z")

</div>

I’m not sure the registry would add much value and would be a negative for me. When I use sentinels, I put them at the top of the module or, if need in multiple modules, in a package utility module. It makes it very easy to know their scope. With a registry, the scope is no longer obvious. Not only would this require additional mental processing for a user, it would make adding static checking more complex.

---

<div class="post-metadata">

**Author:** ![barry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/barry/32/42_2.png) [@barry](https://discuss.python.org/u/barry)\
**Post date:** [March 14, 2025, 3:22pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/240 "2025-03-14T15:22:57Z")

</div>

Hi @taleinat - the SC would really like to move forward with the PEP; this is a friendly ping that if you’d like to discuss the feedback in detail and F2F, please do schedule an office hours visit with us!

Note that the schedule is a little discombobulated at the moment while our members are differently saving\[1\] daylight, but that should settle down by the end of March.

* * *

1. or not!

---

<div class="post-metadata">

**Author:** ![dg-pb](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/dg-pb/32/16248_2.png) [@dg-pb](https://discuss.python.org/u/dg-pb)\
**Post date:** [March 14, 2025, 5:20pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/241 "2025-03-14T17:20:07Z")

</div>

To me the question is whether identity has to be kept on serialization.

* * *

If no, then mental model is very clear:

"It behaves in almost identical manner as `object()` except for ` __repr__ ` (and maybe customizable truth value).

* * *

If yes, then it is a bit of an issue as it either:  
a) Adds a fair amount of complexity  
b) Needs a different spec

As for (b), the simplest way to achieve it that I have found is:

```python
class NULL(Sentinel):
    """Multiline doc for free"""
    # False / True / None (NotImplementedError)
    truth = True

# Sentinel is just convenience baseclass
class Sentinel(metaclass=SentinelMeta):
    pass

# Thus, it could alternatively be:
class NULL(metaclass=SentinelMeta):
    ...

```

I quite like it personally as it keeps things simple, offers all basic features including serialization identity retainment (when defined at module level) without “black magic” and implementation is 12 lines long.

* * *

So I use above in places where I was using `object()` before.

Serialization identity retainment allows it to be used for few more cases, but I am not sure if that is needed. If not, then the above is inferior to this PEP as class creation is much more costly than instantiation.

* * *

Thus, maybe it would be simpler to keep this in line with `object()` with few additional basic features:

1. ` __repr__ `
2. customizable truth value via `truth` arg (maybe)
3. customizable ` __doc__ ` via `doc` argument (maybe)
4. Shorthand `Sentinel.NAME` instantiation. (maybe)

And leave all complexities for a separate concept, which (among other benefits) would allow users to define `Sentinel`s for global/framework wide usage. As per [Singletonobject.c and unification of `singletons` and `singlenels` - #7 by Alex-Wasowicz](https://discuss.python.org/t/singletonobject-c-and-unification-of-singletons-and-singlenels/74550/7)

---

<div class="post-metadata">

**Author:** ![emily](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/emily/32/18934_2.png) [@emily](https://discuss.python.org/u/emily)\
**Post date:** [March 20, 2025, 5:26pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/242 "2025-03-20T17:26:53Z")

</div>

@taleinat The SC’s office hours are now updated and available to book again 🙂

[Previous page](https://discuss.python.org/t/pep-661-sentinel-values/9126.md?page=11)

[Next page](https://discuss.python.org/t/pep-661-sentinel-values/9126.md?page=13)
