# 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:** 17

<div class="post-metadata">

**Author:** ![skirpichev](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/skirpichev/32/10996_2.png) [@skirpichev](https://discuss.python.org/u/skirpichev)\
**Post date:** [April 17, 2026, 6:22am UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/323 "2026-04-17T06:22:14Z")

</div>

Proposed solution fits needs for pure-Python code. How about CPython parts, written in C? Or C extension modules? I’m pretty sure there is a zoo of poor-man replacements for this. E.g. the reduce() function uses `NULL` as default for the `initial` argument. perm() uses `k=None`.

Maybe it was discussed in the looong thread somewhere, but I would appreciate if that use case will be mentioned in the PEP text.

---

<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:** [April 17, 2026, 1:18pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/324 "2026-04-17T13:18:04Z")

</div>

It wasn’t really discussed. I think it makes sense to provide a PySentinel\_New(name, module) C API function. I’m not sure it’s needed for the cases you mention (C functions where NULL or None is acceptable), but it would make sense for replacing e.g. `typing.NoDefault`.

What should the interface look like? I’m leaning towards `PySentinel_New(const char *name, const char *module)`.

---

<div class="post-metadata">

**Author:** ![skirpichev](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/skirpichev/32/10996_2.png) [@skirpichev](https://discuss.python.org/u/skirpichev)\
**Post date:** [April 17, 2026, 6:08pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/325 "2026-04-17T18:08:58Z")

</div>

> [@Jelle](#):
>
> I’m not sure it’s needed for the cases you mention (C functions where NULL or None is acceptable)

Sure, acceptable. But in mentioned cases its a replacement for missing values. Note also, that `NULL` breaks function signature.

> [@Jelle](#):
>
> I’m leaning towards `PySentinel_New(const char *name, const char *module)`.

Why not `PyObject *module`?

---

<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:** [April 17, 2026, 6:35pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/326 "2026-04-17T18:35:55Z")

</div>

Why not a raw string? I am very open to being convinced about this, I don’t write much C.

My thinking was that for the `typing.NoDefault` marker, I’d want to be able to write something like:

```python
no_default = PySentinel_New("NoDefault", "typing");
PyModule_AddObjectRef(m, "NoDefault", no_default):

```

I imagine that’s what most use cases would approximately look like.

---

<div class="post-metadata">

**Author:** ![ngoldbaum](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/ngoldbaum/32/12276_2.png) [@ngoldbaum](https://discuss.python.org/u/ngoldbaum)\
**Post date:** [April 17, 2026, 9:30pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/327 "2026-04-17T21:30:52Z")

</div>

I think this would happen in module initialization code where it’s natural to have a reference to the module object.

---

<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:** [April 18, 2026, 12:36am UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/328 "2026-04-18T00:36:43Z")

</div>

In the typing case, the module object is for the `_typing` module, but we want the object to pretend it is in `typing`. The same would go for `functools.Placeholder`, which is actually created in the `_functools` module.

I think the same thing will happen commonly in third-party code. For example, many numpy classes say they are in the `numpy` module, but in fact are presumably defined in some internal helper module.

---

<div class="post-metadata">

**Author:** ![DanielNoord](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/danielnoord/32/8787_2.png) [@DanielNoord](https://discuss.python.org/u/DanielNoord)\
**Post date:** [April 18, 2026, 10:59am UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/329 "2026-04-18T10:59:47Z")

</div>

Thanks @taleinat for accepting @Jelle’s offer for help. I have done my (admittedly very small) part with a review of Jelle’s [PR](https://github.com/python/peps/pull/4923).

While reviewing I started to wonder whether the PEP should provide guidance for type checkers on how to handle:

```python
MISSING = sentinel('MISSING')
MISSING = sentinel('MISSING')

```

It is very contrived but wouldn’t be surprised if this pattern could emerge in very long modules. Is this a redefinition of `MISSING` that is allowed? Or should this raise an error? Or should each type checker be allowed to choose the behaviour/strictness they want?

_I don’t remember this being discussed in this thread, but I think we can all agree that at this point it is quite hard to remember all the various discussions we have had in it_ 😅

---

<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:** [April 18, 2026, 12:49pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/330 "2026-04-18T12:49:00Z")

</div>

This particular issue wasn’t discussed. We haven’t clearly defined this in the typing spec either for similar use cases. However, we just amended the conformance suite ([remove type statement restrictions unsupported by spec or PEP by carljm · Pull Request #2249 · python/typing · GitHub](https://github.com/python/typing/pull/2249)) to no longer require an error when redefining a type alias; this feels similar.

In any case, this is something that can be changed later with a change to the typing spec.

---

<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:** [April 21, 2026, 1:39pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/331 "2026-04-21T13:39:31Z")

</div>

Tal agreed to add me as a coauthor, and I’m hoping to get the PEP implemented in Python 3.15.

I just merged a PEP change to make `sentinel` a builtin instead of a new module and add a C API: [PEP 661: Update by JelleZijlstra · Pull Request #4923 · python/peps · GitHub](https://github.com/python/peps/pull/4923) . This change is now live at [PEP 661 – Sentinel Values | peps.python.org](https://peps.python.org/pep-0661/) . I also have a draft implementation at [https://github.com/JelleZijlstra/cpython/pull/4](https://github.com/JelleZijlstra/cpython/pull/4) .

I also reopened [PEP 661 -- Sentinel Values · Issue #258 · python/steering-council · GitHub](https://github.com/python/steering-council/issues/258) to ask the Steering Council for a decision.

---

<div class="post-metadata">

**Author:** ![sobolevn](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/sobolevn/32/9274_2.png) [@sobolevn](https://discuss.python.org/u/sobolevn)\
**Post date:** [April 21, 2026, 2:37pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/332 "2026-04-21T14:37:59Z")

</div>

> [@Jelle](#):
>
> a builtin instead of a new module

Sorry, I’ve grepped through this discussion and the PEP itself, I’ve only found the justification of adding this as builtin due to naming problems: [peps/peps/pep-0661.rst at d7f07812decf396f2a50b8b55a43b43d63d63e69 · JelleZijlstra/peps · GitHub](https://github.com/JelleZijlstra/peps/blob/d7f07812decf396f2a50b8b55a43b43d63d63e69/peps/pep-0661.rst#use-a-new-standard-library-module) But, I’ve never found why it was decided not to make it `typing.sentinel`. As it was proposed here [PEP 661: Sentinel Values - #328 by Jelle](https://discuss.python.org/t/pep-661-sentinel-values/9126/328)

---

<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:** [April 21, 2026, 3:10pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/333 "2026-04-21T15:10:26Z")

</div>

The post you link is talking about a specific sentinel (`typing.NoDefault`), not the `sentinel` class itself.

The SC asked to make it a builtin: [PEP 661: Sentinel Values - #234 by barry](https://discuss.python.org/t/pep-661-sentinel-values/9126/234). The motivation was to make the feature easier to use. Putting it in `typing` wouldn’t make much sense because sentinels are not a typing-specific feature.

---

<div class="post-metadata">

**Author:** ![bluetech](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/bluetech/32/8859_2.png) [@bluetech](https://discuss.python.org/u/bluetech)\
**Post date:** [April 21, 2026, 4:29pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/334 "2026-04-21T16:29:56Z")

</div>

The PEP’s Abstract section says “Sentinels can be defined in Python with the `sentinel()` built-in class” but the Specification section is not entirely clear on the class aspect. Is `sentinel` a class? Is `isinstance(sentinel(“FOO”), sentinel)` true?

* * *

The PEP says “Sentinels do not support weakrefs”. Given that there is no global sentinel registry and sentinel objects can presumably be garbage collected, what prevents them from supporting weakrefs? Maybe for efficiency (to avoid the ` __weakref__ ` slot)?

* * *

Overall reading the PEP, sentinels remind me a lot of [ES6 Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) (there are also Lisp/Ruby symbols, but they have different semantics). The similarity was [briefly mentioned](https://discuss.python.org/t/pep-661-sentinel-values/9126/31) up thread by @cben but not discussed further. In JS, `Symbol` is mainly used (AFAIK) to avoid conflicts, for example if you want to stash something on an object, it’s better to do so using a `Symbol` than a string, because it’s unique to you. The main differences seem to be:

1. The ` __module__ ` property on sentinels, which according to the PEP are only added for picklability. The picklability does raise some questions, e.g.

```python
from pickle import loads, dumps

a = sentinel("a")
a2 = sentinel("a")
assert a is not a2
assert loads(dumps(a)) is loads(dumps(a2)) is a is not a2

```

that feels a bit weird.

1. Symbol allows “anonymous” symbols. Seems like Python could support `sentinel()`, by making such instances not picklable.
2. JS has some optional Symbol registry thing, but I think we can disregard it for comparison.

Overall the similarity is enough that it makes me think if it might make sense to basically rename `sentinel` → `symbol`, adding some nice convergence between the world’s two most popular languages. Well, I’m not seriously proposing doing so at this extremely late point, and Python does have `object()`, but still wanted to mention it, since the potential use cases for “give me a unique, type-able, repr-able value” are greater than the sentinel use case.

* * *

> For sentinels defined in a class scope, to avoid potential name clashes, or when a qualified repr would be clearer, one should pass the desired qualified name explicitly

IIUC, this is also needed for pickling to work. Might be worth an explicit mention in this sentence.

I also wonder how this jibes with:

> If the name passed to `sentinel()` does not match the name the object is assigned to, type checkers should emit an error.

---

<div class="post-metadata">

**Author:** ![HexDecimal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hexdecimal/32/28376_2.png) [@HexDecimal](https://discuss.python.org/u/HexDecimal)\
**Post date:** [April 21, 2026, 8:49pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/335 "2026-04-21T20:49:22Z")

</div>

> [@bluetech](#):
>
> The ` __module__ ` property on sentinels, which according to the PEP are only added for picklability. The picklability does raise some questions, e.g.
> 
> ```python
> from pickle import loads, dumps
> 
> a = sentinel("a")
> a2 = sentinel("a")
> assert a is not a2
> assert loads(dumps(a)) is loads(dumps(a2)) is a is not a2
> 
> ```

Pickle will raise on `dumps(a2)` because it verifies the identity of the singleton being pickled. The object at ` __module__ ` with the returned qualified name from ` __reduce__ ` must be the same object being reduced. Thus `a2 = sentinel("a")` is unpicklable and anonymous sentinels are also unpicklable.

* * *

The current qualified name example of `MyClass_NotGiven = sentinel('MyClass.NotGiven')` is malformed with pickle in mind. It should demonstrate how qualified names are used in practice with a class, like is currently shown in the Additional Notes.

To ensure compatibility with pickle singletons it should be clear that the given name is always qualified and the text should not imply that unqualified names are an option (unless the object doesn’t need to be pickled.)

---

<div class="post-metadata">

**Author:** ![HexDecimal](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/hexdecimal/32/28376_2.png) [@HexDecimal](https://discuss.python.org/u/HexDecimal)\
**Post date:** [April 21, 2026, 10:53pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/336 "2026-04-21T22:53:40Z")

</div>

> [@bluetech](#):
>
> Overall the similarity is enough that it makes me think if it might make sense to basically rename `sentinel` → `symbol`, adding some nice convergence between the world’s two most popular languages. Well, I’m not seriously proposing doing so at this extremely late point, and Python does have `object()`, but still wanted to mention it, since the potential use cases for “give me a unique, type-able, repr-able value” are greater than the sentinel use case.

There is a lot of inertia behind `sentinel` but I don’t mind whichever name we end up with. `sentinel` has a narrow definition while the more broad term `symbol` is closer to how I and most others were going to use it anyways considering all the examples we’ve given so far.

I think another language having its own public API term for a unique object meant to avoid collisions means a name change is worth considering. It could be bad for Python to be stuck with something more contrarian, but it could also be that ES6 is being the odd one here.

The current origin for `sentinel` appears to be some public PyPI packages using the term, and its uncertain how much thought those modules put into their decision. We probably accepted it because it was easy not to confuse with other terminology. `symbol` is actually used a lot for programming language internals and might’ve been more confusing to discuss. **In particular, public symbols tend to refer to reflection in most languages.**

`sentinel` vs `symbol` was partially discussed early on, but that was more about whether the objects would collide across modules. This could be another example of `symbol` causing a minor confusion:

> [@PEP 661: Sentinel Values](https://discuss.python.org/t/pep-661-sentinel-values/9126/31):
>
> Another point maybe worth discussing in the PEP is “sentinels” vs “symbols”. In languages that have symbols, they work like “duck enums”, in that 2 independent modules can construct the very same symbol. It seems to me from reading the Rejected ideas there is concensus that’s an anti-goal. We want a way to get a “dedicated and distinct sentinel” without worrying someone else would produce it (other than by importing from the module where it was defined). Plus Python has a long history of usi…

It’d be nice to have clear rationale for the name choice.

---

<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:** [April 23, 2026, 11:21pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/337 "2026-04-23T23:21:35Z")

</div>

On behalf of the Python Steering Council, I’m very happy to share that we have accepted PEP 661 for Python 3.15. Thank you @taleinat for creating the PEP and @Jelle for reviving it in time for 3.15. We especially appreciate how all of the SC’s earlier feedback was addressed nicely in the latest iteration of the PEP, resolving all of our previous concerns. Congrats!

---

<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:** [April 24, 2026, 3:46am UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/338 "2026-04-24T03:46:40Z")

</div>

Thank you to the Steering Council for the quick action on this PEP!

The implementation is in [gh-148829: Implement PEP 661 by JelleZijlstra · Pull Request #148831 · python/cpython · GitHub](https://github.com/python/cpython/pull/148831) . Please help review it so we can get this feature merged in an excellent state for Python 3.15.

---

<div class="post-metadata">

**Author:** ![DanielNoord](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/danielnoord/32/8787_2.png) [@DanielNoord](https://discuss.python.org/u/DanielNoord)\
**Post date:** [April 24, 2026, 6:26am UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/339 "2026-04-24T06:26:23Z")

</div>

Also thank you @taleinat for starting this PEP many years ago and allowing Jelle to help push it over the finish line. Can’t wait to use this!

---

<div class="post-metadata">

**Author:** ![StelarDream](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/stelardream/32/35414_2.png) [@StelarDream](https://discuss.python.org/u/StelarDream)\
**Post date:** [April 28, 2026, 10:16pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/340 "2026-04-28T22:16:54Z")

</div>

Hi all, first post here ^^

I came across this PEP through a YouTube video covering upcoming 3.15 features, and since it was just accepted I figured it wasn’t too late to share a few thoughts. I haven’t read the full thread (337 posts is a lot!), so apologies if any of this has already been covered.

I’m not a CPython internals person, I mostly encounter sentinels from the typing side, as a way to express “this argument was not provided” in a signature.

**One small concrete thing first**

The [3.15 docs](https://docs.python.org/3.15/library/functions.html#sentinel) don’t mention the subclassing restriction at all. A user who tries `class MySentinel(sentinel): ...` gets a `TypeError` with no explanation and no pointer toward `Enum` as the grouped-sentinel alternative. Even if the ban stays for good reasons, that seems worth a documentation note regardless.

**On the subclassing restriction itself**

I didn’t find a rationale for the ` __init_subclass__ ` block anywhere, not in the PEP body, not in Rejected Ideas. The concern I can imagine is that `class MISSING(sentinel): ...` looks like a sentinel instance rather than a factory, which is fair. But the useful pattern doesn’t look like that:

```python
class FalsySentinel(sentinel):
    def __bool__ (self) -> bool:
        return False

MISSING = FalsySentinel("MISSING")

```

Lowercase name, explicit instantiation, the ambiguity mostly goes away. And ` __bool__ ` customisation was listed as a dropped feature in the PEP due to “added complexity,” but with subclassing it would just be… normal Python. No new API surface needed.

That said, I think I can see _why_ the ban might exist technically. The ` __init__ ` uses `sys._getframemodulename(1)` with a hardcoded frame depth to capture the calling module for pickling, and a `super(). __init__ ()` call from a subclass shifts that frame. That’s a real problem in pure Python. But since `sentinel` ships as a C builtin, that seems like something addressable at that level, which makes me genuinely unsure whether the ban reflects a considered design decision, or just that nobody needed subclassing badly enough to do the extra work.

I’d love to hear if that was actually discussed somewhere in the thread!

---

<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:** [April 28, 2026, 10:38pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/341 "2026-04-28T22:38:03Z")

</div>

This should indeed be mentioned in the docs, thanks for catching that. Please send a PR or issue (I’ll try to get to it but might forget).

As for the motivation, subclassing builtins is often somewhat problematic and can lead to confusing behavior. What you mention about module inference is one example. That’s why I preferred to keep the behavior more restricted here for now. That said, this is something we can revisit in the future, and if good use cases emerge we can enable subclassing in 3.16.

---

<div class="post-metadata">

**Author:** ![StelarDream](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/stelardream/32/35414_2.png) [@StelarDream](https://discuss.python.org/u/StelarDream)\
**Post date:** [April 28, 2026, 11:44pm UTC](https://discuss.python.org/t/pep-661-sentinel-values/9126/342 "2026-04-28T23:44:09Z")

</div>

> [@Jelle](#):
>
> This should indeed be mentioned in the docs, thanks for catching that. Please send a PR or issue (I’ll try to get to it but might forget).

Done! Filed the issue at [gh-149119](https://github.com/python/cpython/issues/149119) and went ahead with a PR at [gh-149120](https://github.com/python/cpython/pull/149120).

Was a good excuse to make my first open source contribution. Thanks for the nudge!

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

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