Значение None в Python часто выполняет сразу несколько ролей: обозначает отсутствие данных, неустановленный параметр, ошибку или вполне допустимое содержимое. Из-за этого кэш не может различить отсутствующий ключ и ключ со значением None, а аргумент timeout=None неясно трактовать: отменить тайм-аут или сохранить прежний. Sentinel-значения решают проблему, находясь за пределами домена обычных данных и не совпадая ни с одним допустимым значением.
В Python и библиотеках такие маркеры уже применяются. Ellipsis используется в срезах NumPy и stub-файлах, NotImplemented позволяет бинарным операторам передать выполнение другой реализации, а MISSING в Pydantic отличает явно переданный null от поля, которого вообще не было в запросе. В Pydantic 2.14 последний механизм вышел из экспериментального статуса.
Собственные sentinel раньше создавали через MISSING = object(). Во время выполнения приём работает, но статические анализаторы не могут нормально сужать типы. TypeIs, Literal и Enum с одним членом имеют собственные ограничения, а проверки идентичности могут ломаться при копировании и сериализации pickle.
PEP 661 сначала отложили, но девять дней спустя Steering Council его принял. В Python 3.15 появился встроенный sentinel: вызов sentinel("MISSING") создаёт маркер, который анализатор типов способен отличать от str и None так же, как None. В примере обновления email MISSING означает «ничего не менять», None — удалить адрес, а строка — сохранить новое значение.
Для старых версий Python доступен backport в typing_extensions. Однако поддержка анализаторов ещё развивается, поэтому автор советует проверить используемый инструмент. Его общая рекомендация — явно продумывать смысл отсутствующего значения, применять sentinel, уже предоставленный библиотекой, и постепенно заменять прежние обходные приёмы встроенным решением. Такой подход делает поведение функций понятнее и снижает риск ошибок при дальнейшем сопровождении программного кода.