The pattern that's being proposed here (`A = Sentinel("A")`) is similar to `TypeVar`, which type checkers special case. In particular, they enforce that the literal name passed to the `TypeVar` constructor matches the name of the variable to which it is assigned. Following this precedent, one could say that type checkers should enforce the same constraint for `Sentinel`, and a name mismatch would be flagged as an error. Only an assignment to a matching variable name (`A = Sentinel("A")`) would be allowed. This proposal differs from `TypeVar` in that the resulting variable is not a type that can be used by itself within a type annotation. Rather, it's a value that is used at runtime and must be wrapped in some other type (like `Literal`) to be used within a type annotation. I think that using `Literal[A]` to spell the type has problems from a usability perspective. It's not clear from looking at this type that it represents a sentinel. Increasingly, Python developers are relying on type information within function signatures to guide them as they code. If I were to see a parameter with type `int | Literal[A]`, I would be confused about what it means. Let me offer a different proposal that addresses the usability problem and has the additional benefit that it already works with all type checkers today (doesn't require any new special casing), and it doesn't require any new runtime support. It wouldn't even require a new PEP, just a small change to the existing typing.pyi type stub that would be entirely backward compatible with all versions of Python. The proposal is to simply use a class as a sentinel. For readability, we would define a new type alias within `typing.pyi` called `Sentinel` that simply aliases `type[_T]`. ```python # This type alias would be added to typing.pyi. Sentinel = type[_T] ``` ```python # Defining a new sentinel would look like this. Note the `final` decorator. @final class MISSING: pass ``` ```python # Using the sentinel in a type signature would look like this. def func(value: int | Sentinel[MISSING] = MISSING): if value is MISSING: print("Received sentinel") return reveal_type(value) # should reveal "int" ``` This proposal would benefit from one small improvement to mypy and pyright (and presumably the other type checkers). We'd want to add type narrowing support for the `A is B` pattern where `B` is a final class. That's relatively easy to do and applies more broadly to use cases beyond sentinels. The latest draft of PEP 661 considers something similar to my proposal but rejects it for reasons that I don't understand. (Refer to the "Using class objects" header in the "Rejected Ideas" section.) My proposal above doesn't allow for customization of the `__repr__`. If that's deemed important by the developer who defines the sentinel, it can be accommodated through the use of a metaclass, requiring just a few additional lines of code: ```python class SentinelMeta(type): def __repr__(cls): return f"<{cls.__name__}>" @final class MISSING(metaclass=SentinelMeta): ... ``` In summary, I think this proposal addresses all of the requirements of a sentinel, is typing friendly (and supports strict typing), addresses usability issues for language servers, doesn't require any new runtime support, doesn't require any special-case handling in type checkers, doesn't introduce any backward compatibility issues, could be rolled out immediately rather than waiting for multiple years, and eliminates the need for a PEP and the associated ratification process. Thoughts? -Eric -- Eric Traut Contributor to pyright & pylance Microsoft