I think the need for a custom sentinel value already takes us well beyond the domain of "an average Python developer". The most common sentinel in Python is `None`, and it works well in the vast majority of use cases. Plus, `None` enjoys support from the runtime (which enforces the fact that it is a singleton) and extensive support in all type checkers. For those reasons, `None` will continue to be used as a sentinel value in the vast majority of use cases even after support is added for custom sentinel values. I appreciate the need for custom sentinel values, but these use cases are relatively rare and are typically constrained to library authors who are (almost by definition) more advanced developers. Library authors are an important and vocal constituency, but they're small in number compared to library consumers. That's something we should keep in mind when considering the relative importance of "readability for the consumer" vs "conciseness of implementation for the author". The more I think about this, the more problems I see with the construct `MISSING = Sentinel("MISSING")`. It begs the question: should a type checker treat the symbol `MISSING` as a variable or a type alias? There are very different rules for the two, so it's important to specify which semantics are implied. Type aliases can be declared only once, whereas variables can be assigned multiple times (possibly within different conditional code blocks). For that reason, determining the type of a variable typically requires code flow analysis, except in the case where its type is explicitly annotated. For this reason (and others), variables are not allowed to be used within type annotations. Type annotations are allowed to reference symbols that have not yet appeared within the file (i.e. forward declarations), so code-flow ordering cannot be used for symbols that appear within a type annotation. Any use of a variable within a type annotation is therefore considered an error today by most type checkers, and it's important that we retain that constraint. For that reason, `MISSING` cannot be considered a variable if it's going to appear in type annotations like `Literal[MISSING]`. What if we treat `MISSING` as a type alias rather than a variable? Type checkers would then enforce that it is assigned only once — and only in the module (global) scope. However, unlike other type aliases, the value of `MISSING` is used at runtime (as a sentinel value). Even more problematic, if `MISSING` is a treated as a type alias, what type would be implied by the type annotation `MISSING` on its own? And why would it need to be wrapped in a `Literal`? The draft PEP 661 and Alex's proposal both suffer from these problems. My class-based proposal uses existing constructs and semantics — both at runtime and within the type system, so it doesn't introduce new special cases or inconsistencies. All of the type system rules are well established and clear. And the resulting type is easily understood by consumers a library that uses a custom sentinel value in its interface. I still haven't heard any reasons I find compelling for why the class-based proposal isn't the best approach. However, if the consensus is that we need to use "simple objects" rather than classes, then I would recommend against using `Literal` to describe the type. Instead, I would treat it as a type alias, in which case it should be used without a `Literal` wrapper. Here's how that would look in practice: ```python def func(value: int | MISSING = MISSING): ... ``` This suffers from the fact that (as I mentioned above) `MISSING` is used inconsistently as both a type alias and a value. The identifier `MISSING` appears twice in the function signature but has two different semantic meanings. I find this to be a bit confusing, but there is at least a precedent for it: `None`. You will see `None` used in a similar fashion as above: ```python def func(value: int | None = None): ... ``` -Eric -- Eric Traut Contributor to pyright & pylance Microsoft