# PEP 661 – 센티널 값, 5년 뒤 승인됨

> Clean Markdown view of GeekNews topic #29123. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=29123](https://news.hada.io/topic?id=29123)
- GeekNews Markdown: [https://news.hada.io/topic/29123.md](https://news.hada.io/topic/29123.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-05-03T20:30:20+09:00
- Updated: 2026-05-03T20:30:20+09:00
- Original source: [peps.python.org](https://peps.python.org/pep-0661/)
- Points: 1
- Comments: 1

## Topic Body

- **PEP 661**은 `None`이 유효한 값인 상황에서 별도로 구별할 수 있는 센티널 값을 만들기 위해 Python 내장 호출 가능 객체 `sentinel()`과 C API `PySentinel_New()`를 제안함  
- 기존 `_sentinel = object()` 관용구는 함수 시그니처에서 `repr`이 길고 불명확하며, 명확한 타입 시그니처·복사·피클링에서 문제가 생길 수 있음  
- **`sentinel('MISSING')`** 호출은 짧은 `repr`을 가진 새 고유 객체를 만들며, 같은 센티널을 공유하려면 `MISSING = sentinel('MISSING')`처럼 변수에 할당해 명시적으로 재사용해야 함  
- 센티널은 `is`로 비교하는 방식이 권장되고 참값으로 평가되며, `copy.copy()`와 `copy.deepcopy()`는 같은 객체를 반환하고, 모듈에서 이름으로 임포트 가능한 경우 피클링 뒤에도 항등성을 보존함  
- 타입 시스템은 `int | MISSING`처럼 센티널 자체를 타입 표현식에 사용할 수 있게 하며, 최신 공식 문서는 Python 3.15의 [`sentinel`](<https://docs.python.org/3.15/library/functions.html#sentinel "(in Python v3.15>)") 문서에 있음  
  
---  
  
### 도입 배경  
- 고유한 자리표시자 값인 **센티널 값(sentinel value)** 은 함수 인자가 주어지지 않았을 때의 기본값, 탐색 실패를 나타내는 반환값, 누락 데이터를 나타내는 값 등에 쓰임  
- Python에는 보통 이런 용도로 쓰는 특수 값 `None`이 있지만, `None` 자체가 유효한 값인 문맥에서는 `None`과 구별되는 별도 센티널 값이 필요함  
- 2021년 5월 python-dev 메일링 리스트에서 `traceback.print_exception`에 쓰이는 센티널 값을 더 낫게 구현하는 방법이 논의됨  
- 기존 구현은 흔한 관용구인 `_sentinel = object()`를 사용했지만, `repr`이 지나치게 길고 정보가 부족해 함수 시그니처가 읽기 어려워짐  
  ```python  
  >>> help(traceback.print_exception)  
  Help on function print_exception in module traceback:  
  
  print_exception(exc, /, value=<object object at  
  0x000002825DF09650>, tb=&lt;object object at 0x000002825DF09650&gt;,  
  limit=None, file=None, chain=True)  
  ```  
- 논의 과정에서 기존 센티널 구현의 다른 문제도 확인됨  
  - 일부 센티널은 고유한 타입이 없어, 센티널을 기본값으로 쓰는 함수의 명확한 타입 시그니처를 정의하기 어려움  
  - 복사 후 별도 인스턴스가 생겨 `is` 비교가 실패하는 등 예상과 다르게 동작함  
  - 일부 흔한 관용구는 피클링 후 언피클링했을 때도 비슷한 문제가 있음  
- Victor Stinner가 Python 표준 라이브러리에서 쓰이는 센티널 값 목록을 제공했고, 표준 라이브러리 안에서도 여러 구현 방식이 쓰이며 많은 구현이 위 문제 중 하나 이상을 갖는다는 점이 확인됨  
- discuss.python.org의 투표는 39표 기준으로 명확한 결론을 내지 못함  
  - 40%는 “현 상태가 괜찮고 일관성이 필요 없다”를 선택함  
  - 다수는 하나 이상의 표준화된 해법을 선택함  
  - 37%는 “새로운 전용 센티널 팩토리/클래스/메타클래스를 일관되게 사용하고, 표준 라이브러리에서 공개 제공”하는 선택지를 골랐음  
- 엇갈린 결과 때문에 PEP가 작성됐고, 단순하고 좋은 표준 라이브러리 구현이 표준 라이브러리 내부와 외부 모두에 유용하다는 결론으로 이어짐  
- 표준 라이브러리의 기존 센티널을 모두 이 방식으로 바꾸는 것은 필수 사항이 아니며, 해당 유지보수자의 재량에 맡겨짐  
- PEP 문서는 역사적 문서이며, 최신 공식 문서는 Python 3.15의 [`sentinel`](<https://docs.python.org/3.15/library/functions.html#sentinel "(in Python v3.15>)") 문서에 있음  
  
### 설계 기준  
- 센티널 객체는 `is` 연산자로 비교했을 때 자기 자신과는 항상 동일하고, 다른 어떤 객체와도 동일하지 않아야 함  
- 센티널 객체 생성은 단순하고 직관적인 **한 줄 코드**여야 함  
- 필요한 만큼 여러 개의 서로 다른 센티널 값을 쉽게 정의할 수 있어야 함  
- 센티널 객체는 짧고 명확한 `repr`을 가져야 함  
- 센티널에 대해 명확한 타입 시그니처를 사용할 수 있어야 함  
- 복사 후에도 올바르게 동작해야 하며, 피클링과 언피클링 시 예측 가능한 동작을 가져야 함  
- CPython 3.x와 PyPy3에서 동작해야 하며, 가능하면 다른 Python 구현에서도 동작해야 함  
- 구현과 사용 모두 최대한 단순하고 직관적이어야 하며, Python을 배울 때 또 하나의 특수한 개념으로 부담이 되지 않아야 함  
- 표준 라이브러리는 `sentinels`나 `sentinel` 같은 PyPI 패키지 구현에 의존할 수 없기 때문에, 표준 라이브러리 안에서 사용할 수 있는 구현이 필요함  
  
### `sentinel()` 사양  
- 새 내장 호출 가능 객체 **`sentinel`** 이 추가됨  
  ```python  
  >>> MISSING = sentinel('MISSING')  
  >>> MISSING  
  MISSING  
  ```  
- `sentinel()`은 위치 전용 인자 `name` 하나를 받으며, `name`은 반드시 `str`이어야 함  
- 문자열이 아닌 값을 전달하면 `TypeError`가 발생함  
- `name`은 센티널의 이름과 `repr`로 쓰임  
- 센티널 객체는 두 개의 공개 속성을 가짐  
  - `__name__`: 센티널 이름  
  - `__module__`: `sentinel()`이 호출된 모듈 이름  
- `sentinel`은 서브클래싱할 수 없음  
- `sentinel(name)`을 호출할 때마다 새 센티널 객체가 반환됨  
- 같은 센티널을 여러 곳에서 써야 하면, 기존 `MISSING = object()` 관용구처럼 변수에 할당한 뒤 같은 객체를 명시적으로 재사용해야 함  
  ```python  
  MISSING = sentinel('MISSING')  
  
  def read_value(default=MISSING):  
      ...  
  ```  
- 특정 값이 센티널인지 확인할 때는 `None`과 마찬가지로 **`is` 연산자**를 쓰는 방식이 권장됨  
- `==` 비교도 자기 자신과 비교할 때만 `True`를 반환하도록 기대대로 동작함  
- `if value is MISSING:` 같은 항등성 검사가 보통 `if value:` 또는 `if not value:` 같은 불리언 검사보다 적절함  
- 센티널 객체는 참값(truthy)이며, 불리언 평가 결과가 `True`임  
  - 이는 임의 클래스의 기본 동작 및 `Ellipsis`의 불리언 값과 같음  
  - 거짓값(falsy)인 `None`과는 다름  
- `copy.copy()` 또는 `copy.deepcopy()`로 센티널 객체를 복사하면 같은 객체가 반환됨  
- 정의된 모듈에서 이름으로 임포트 가능한 센티널은 표준 피클 메커니즘에 따라 피클링과 언피클링 후에도 항등성을 보존함  
  ```python  
  MISSING = sentinel('MISSING')  
  assert pickle.loads(pickle.dumps(MISSING)) is MISSING  
  ```  
- `sentinel()`은 센티널 생성 시 호출 모듈을 `__module__` 속성으로 기록함  
- 피클링은 센티널을 모듈과 이름으로 기록하고, 언피클링은 모듈을 임포트한 뒤 이름으로 센티널을 가져옴  
- 지역 스코프에서 생성되고 모듈 전역 또는 클래스 속성의 일치하는 이름에 할당되지 않은 센티널처럼, 모듈과 이름으로 임포트할 수 없는 센티널은 피클링할 수 없음  
- 센티널 객체의 `repr`은 `sentinel()`에 전달한 `name`이며, 암묵적인 모듈 한정자는 붙지 않음  
- 한정된 `repr`이 필요하면 이름에 명시적으로 포함해야 함  
  ```python  
  >>> MyClass_NotGiven = sentinel('MyClass.NotGiven')  
  >>> MyClass_NotGiven  
  MyClass.NotGiven  
  ```  
- 센티널 객체의 순서 비교는 정의되지 않음  
- 센티널은 weakref를 지원하지 않음  
  
### 타입 지정  
- 타입이 지정된 Python 코드에서 센티널 사용을 명확하고 단순하게 만들기 위해, 타입 시스템에 센티널 객체를 위한 특수 처리가 추가됨  
- 센티널 객체는 [타입 표현식](<https://typing.python.org/en/latest/spec/glossary.html#term-type-expression "(in typing>)") 안에서 자기 자신을 나타내는 값으로 사용할 수 있음  
- 이는 기존 타입 시스템에서 `None`을 다루는 방식과 유사함  
  ```python  
  MISSING = sentinel('MISSING')  
  
  def foo(value: int | MISSING = MISSING) -> int:  
      ...  
  ```  
- 타입 검사기는 `NAME = sentinel('NAME')` 형태의 센티널 생성을 새 센티널 객체 생성으로 인식해야 함  
- `sentinel()`에 전달한 이름이 할당 대상 이름과 일치하지 않으면 타입 검사기는 오류를 내야 함  
- 이 문법으로 정의된 센티널은 [타입 표현식](<https://typing.python.org/en/latest/spec/glossary.html#term-type-expression "(in typing>)")에서 사용할 수 있음  
- 해당 센티널 타입은 센티널 객체 자체 하나만 멤버로 갖는 [완전 정적 타입](<https://typing.python.org/en/latest/spec/glossary.html#term-fully-static-type "(in typing>)")을 나타냄  
- 타입 검사기는 `is`와 `is not` 연산자를 사용해 센티널이 포함된 유니언 타입 좁히기를 지원해야 함  
  ```python  
  from typing import assert_type  
  
  MISSING = sentinel('MISSING')  
  
  def foo(value: int | MISSING) -> None:  
      if value is MISSING:  
          assert_type(value, MISSING)  
      else:  
          assert_type(value, int)  
  ```  
- 런타임 구현은 타입 표현식 사용을 지원하기 위해 `__or__`와 `__ror__` 메서드를 가져야 하며, 이 메서드는 [`typing.Union`](<https://docs.python.org/3/library/typing.html#typing.Union "(in Python v3.14>)") 객체를 반환함  
- Typing Council은 이 제안의 타입 관련 부분을 [지지](https://github.com/python/steering-council/issues/258#issuecomment-2417524379)함  
  
### C API  
- C 확장에서도 센티널이 유용할 수 있어, 두 개의 새 C API 함수가 제안됨  
- `PyObject *PySentinel_New(const char *name, const char *module_name)`은 새 센티널 객체를 생성함  
- `bool PySentinel_Check(PyObject *obj)`는 객체가 센티널인지 확인함  
- C 코드는 특정 센티널인지 확인할 때 `==` 연산자를 사용할 수 있음  
  
### 호환성과 보안  
- 새 내장 이름을 추가하면, 현재 bare name `sentinel`이 `NameError`를 발생시킨다고 가정하는 코드는 더 이상 같은 결과를 보지 않음  
- 이는 새 내장 이름 추가에서 일반적으로 생기는 호환성 고려사항임  
- 이미 존재하는 로컬, 전역, 임포트 이름 `sentinel`은 영향을 받지 않음  
- 이미 `sentinel`이라는 이름을 쓰는 코드는 새 내장 객체를 쓰도록 조정해야 할 수 있으며, 내장 이름과의 충돌을 경고하는 린터에서 새 경고를 받을 수 있음  
- 새 내장 기능에 대한 일반 문서화 방식인 독스트링, 라이브러리 문서, “What’s New” 섹션으로 충분하다고 봄  
- 이 제안에는 보안 영향이 없다고 봄  
  
### 참조 구현과 백포트  
- 참조 구현은 CPython 풀 리퀘스트 [[10]](https://peps.python.org/pep-0661/#id24)로 제공됨  
- 이전 참조 구현은 별도 GitHub 저장소 [[7]](https://peps.python.org/pep-0661/#id21)에 있음  
- 의도된 동작의 스케치는 다음과 같음  
  ```python  
  class sentinel:  
      """Unique sentinel values."""  
  
      __slots__ = ("__name__", "_module_name")  
  
      def __init_subclass__(cls):  
          raise TypeError("type 'sentinel' is not an acceptable base type")  
  
      def __init__(self, name, /):  
          if not isinstance(name, str):  
              raise TypeError("sentinel name must be a string")  
          self.__name__ = name  
          self._module_name = sys._getframemodulename(1)  
  
      @property  
      def __module__(self):  
          return self._module_name  
  
      def __repr__(self):  
          return self.__name__  
  
      def __reduce__(self):  
          return self.__name__  
  
      def __copy__(self):  
          return self  
  
      def __deepcopy__(self, memo):  
          return self  
  
      def __or__(self, other):  
          return typing.Union[self, other]  
  
      def __ror__(self, other):  
          return typing.Union[other, self]  
  ```  
  - [typing-extensions](https://pypi.org/project/typing-extensions/) 모듈에는 [백포트](https://typing-extensions.readthedocs.io/en/latest/#typing_extensions.Sentinel)가 있지만, 현재 PEP 반복판의 동작과 정확히 일치하지는 않음  
  
  ### 거절된 대안  
  - ## `NotGiven = object()` 사용  
    - 이 방식은 PEP의 설계 기준에서 다룬 단점을 모두 가짐  
    - `repr`이 길고 명확하지 않으며, 타입 시그니처를 명확히 하기 어렵고, 복사 또는 피클링 관련 문제가 생길 수 있음  
  - ## `MISSING` 또는 `Sentinel` 같은 단일 새 센티널 값 추가  
    - 하나의 값이 여러 곳에서 여러 용도로 쓰이면, 어떤 사용 사례에서는 그 값 자체가 유효한 값이 아닐 것이라고 항상 확신하기 어려움  
    - 전용의 서로 다른 센티널 값은 잠재적 엣지 케이스를 고려하지 않고 더 자신 있게 사용할 수 있음  
    - 센티널 값에는 사용 문맥에 맞는 의미 있는 이름과 `repr`을 제공할 수 있어야 함  
    - 이 선택지는 투표에서 12%만 선택해 인기가 매우 낮았음  
  - ## 기존 `Ellipsis` 센티널 값 사용  
    - `Ellipsis`는 원래 이런 용도로 의도된 값이 아님  
    - `pass` 대신 빈 클래스나 함수 블록을 정의하는 데 쓰이는 일이 늘었지만, 전용의 서로 다른 센티널 값만큼 모든 경우에 자신 있게 사용할 수 없음  
  - ## 단일 값 `Enum` 사용  
    - 제안된 관용구는 다음과 같음  
    ```python  
    class NotGivenType(Enum):  
      NotGiven = 'NotGiven'  
      NotGiven = NotGivenType.NotGiven  
    ```  
  - 반복이 지나치고, `repr`이 `<NotGivenType.NotGiven: 'NotGiven'>`처럼 너무 김  
  - 더 짧은 `repr`을 정의할 수는 있지만, 코드와 반복이 더 늘어남  
  - 투표의 9개 선택지 중 유일하게 표를 받지 못해 가장 인기가 낮았음  
- ## 센티널 클래스 데코레이터  
  - 제안된 관용구는 다음과 같음  
    ```python  
    @sentinel  
    class NotGivenType: pass  
    NotGiven = NotGivenType()  
    ```  
  - 데코레이터 구현 자체는 단순하고 명확할 수 있지만, 관용구가 너무 장황하고 반복적이며 기억하기 어려움  
- ## 클래스 객체 사용  
  - 클래스는 본질적으로 싱글턴이므로 센티널 값으로 쓰는 발상은 가능함  
  - 가장 단순한 형태는 다음과 같음  
    ```python  
    class NotGiven: pass  
    ```  
    - 명확한 `repr`을 얻으려면 메타클래스나 클래스 데코레이터가 필요함  
    ```python  
    class NotGiven(metaclass=SentinelMeta): pass  
    ```  
    ```python  
    @Sentinel  
    class NotGiven: pass  
    ```  
  - 클래스를 이런 방식으로 쓰는 것은 이례적이라 혼란스러울 수 있음  
  - 주석 없이는 코드 의도를 이해하기 어렵고, 센티널이 호출 가능해지는 등 예상 밖의 바람직하지 않은 동작이 생김  
- ## 구현 없이 권장 표준 관용구만 정의  
  - 흔한 기존 관용구 대부분은 중요한 단점을 가짐  
  - 지금까지 이런 단점을 피하면서 명확하고 간결한 관용구는 발견되지 않았음  
  - 관련 투표에서 관용구 권장 선택지는 인기가 낮았고, 가장 많은 표를 받은 선택지도 25%에 그쳤음  
- ## 새 표준 라이브러리 모듈 사용  
  - 초기 초안은 새 `sentinels` 또는 `sentinellib` 모듈에 `Sentinel` 클래스를 추가하는 방식을 제안함  
  - 공개 호출 가능 객체 하나를 위해 새 모듈을 추가하는 것은 불필요함  
  - 모듈을 사용하면 기존 `object()` 관용구보다 기능 사용이 불편해짐  
  - Steering Council도 `object()`만큼 쉽게 쓰이도록 내장 기능으로 만들 것을 구체적으로 권장함  
  - `sentinels`라는 이름은 이미 활발히 쓰이는 PyPI 패키지와 충돌하며, 내장 기능으로 만들면 이름 문제를 피할 수 있음  
- ## 모듈별 센티널 이름 레지스트리 사용  
  - 초기 초안은 센티널 이름을 모듈 안에서 고유하게 만들도록 제안함  
  - 이 설계에서는 같은 모듈에서 `sentinel("MISSING")`을 반복 호출하면, 모듈 이름과 센티널 이름을 키로 하는 프로세스 전역 레지스트리를 통해 같은 객체를 반환함  
  - 이 동작은 지나치게 암묵적이어서 거절됨  
  - 공유 센티널이 필요하면 기존 `MISSING = object()`처럼 하나를 명시적으로 정의하고 이름으로 재사용하면 됨  
  - 지역 스코프에서는 호출이나 반복마다 새 센티널을 원할 수도 있으므로, `sentinel(name)` 반복 호출은 `object()` 반복 호출처럼 서로 다른 객체를 만들어야 함  
  - 레지스트리를 제거하면 구현과 사고 모델이 더 단순해지고, `sentinel(name)`은 `repr`이 `name`인 새 고유 객체를 만든다는 규칙만 남음  
- ## 모듈 이름 자동 발견 또는 전달  
  - 초기 초안은 레지스트리 기반 설계를 지원하기 위해 선택적 `module_name` 인자를 제안함  
  - 레지스트리가 제거되면서 공개 `module_name` 인자는 핵심 제안에 더 이상 필요하지 않음  
  - 구현은 `TypeVar`와 유사하게 피클이 임포트 가능한 센티널을 모듈과 이름으로 직렬화할 수 있도록 호출 모듈을 내부적으로 기록함  
  - 내부 모듈 이름은 센티널의 `repr`에 영향을 주지 않음  
  - 모듈명이나 클래스명이 포함된 `repr`을 원하면 `sentinel("mymodule.MISSING")`처럼 단일 `name` 인자에 명시적으로 포함하면 됨  
- ## `repr` 사용자 지정 허용  
  - 기존 센티널 값을 `repr` 변경 없이 이 방식으로 옮길 수 있다는 장점이 있었음  
  - 하지만 추가 복잡도를 감수할 가치가 없다고 보고 제외됨  
- ## 불리언 평가 사용자 지정 허용  
  - 논의에서는 센티널을 명시적으로 참값, 거짓값, 또는 `bool` 변환 불가로 만들 수 있게 하는 방안이 검토됨  
  - 일부 서드파티 센티널은 거짓값 동작을 공개 API의 일부로 제공함  
  - 여러 참여자는 불리언 문맥에서 예외를 발생시키는 편이 항등성 검사를 더 잘 강제한다고 보았음  
  - PEP는 일반 객체의 기본 참값 동작을 유지하고 항등성 검사를 권장하는 방식으로 초기 제안을 단순하게 유지함  
  - 사용자 지정 불리언 동작은 추가 API와 타입 지정 복잡도를 감수할 만하다고 판단될 때 나중에 검토될 수 있음  
- ## 타입 애너테이션에서 `typing.Literal` 사용  
  - 논의에서 여러 사람이 제안했고, PEP도 처음에는 이 방식을 채택했음  
  - 그러나 `Literal["MISSING"]`이 센티널 값 `MISSING`에 대한 전방 참조가 아니라 문자열 값 `"MISSING"`을 가리키기 때문에 혼란을 일으킬 수 있음  
  - bare name 사용도 논의에서 자주 제안됨  
  - bare name 방식은 `None`이 만든 선례와 잘 알려진 패턴을 따르며, 임포트가 필요 없고 훨씬 짧음  
  
### 추가 사용 지침  
- 클래스 스코프에서 센티널을 정의하거나, 이름 충돌을 피하거나, 한정된 `repr`이 더 명확한 경우에는 원하는 한정 이름을 명시적으로 전달해야 함  
  ```python  
  >>> class MyClass:  
  ...    NotGiven = sentinel('MyClass.NotGiven')  
  >>> MyClass.NotGiven  
  MyClass.NotGiven  
  ```  
- 함수나 메서드 안에서 센티널을 만드는 것은 허용됨  
- `sentinel()` 호출마다 서로 다른 객체가 생성되므로, 지역 스코프에서 만든 센티널은 그 스코프에서 `object()`를 호출해 만든 값처럼 동작함  
- `NotImplemented`의 불리언 값은 `True`이지만, Python 3.9부터 이를 사용하는 것은 폐기 예정이며 deprecation warning을 발생시킴  
- 이 폐기는 bpo-35712 [[8]](https://peps.python.org/pep-0661/#id22)에 설명된 `NotImplemented` 고유의 문제 때문임  
- 여러 개의 관련 센티널 값을 정의해야 하거나 이들 사이에 순서를 정의해야 한다면, `Enum` 또는 유사한 방식을 사용해야 함  
- 이러한 센티널의 타입 지정에 대해서는 typing-sig 메일링 리스트 [[9]](https://peps.python.org/pep-0661/#id23)에서 여러 옵션이 논의됨

## Comments



### Comment 56758

- Author: neo
- Created: 2026-05-03T20:30:20+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/mi9xrw/pep_661_sentinel_values_accepted_5_years) 
- 선택한 이름이 [의미가 너무 좁은](https://en.wikipedia.org/wiki/Sentinel_value) 것 같아 이상함  
  이름만 보면 **고유 심볼** 같은 쪽이 더 유연한 기본 요소였을 듯함. 실제로는 거의 심볼처럼 동작할 테니 그렇게 쓸 수는 있겠지만, 이름을 “Sentinels”로 붙인 건 어색함. Lisp에 익숙해서 그렇게 느끼는 걸 수도 있음
  - 목표는 `SENTINEL_A`가 `SENTINEL_B`와 **다른 타입**이 되게 해서, 어떤 값이 `is_a SENTINEL_A`인지 물을 수 있게 하는 것 같음  
    Ruby의 심볼은 그렇게 동작하지 않음: `:beef.is_a? :droog.class #=> true`
  - Lisp식 사고가 맞음. 넓은 용도로 쓰는 게 바람직하고 해결해야 할 문제라고 전제하고 있지만, Python에는 이미 Lisp 심볼의 대부분 사용 사례를 위한 **`Literal`** 과 리터럴 문자열이 있음  
    이것들이 이름 있는 센티널인 이유는 [sentinel values](https://python-patterns.guide/python/sentinel-object/)가 Python에서 흔한 개념이자 패턴이고, 센티널은 그 패턴 사용에서 생기는 일부 문제를 좁게 해결하려는 것이기 때문임. “Motivation”과 “Rationale” 절에서 설명한 그대로임  
    또한 센티널은 값 의미론을 갖지 않으므로, 같은 이름의 센티널 두 개도 서로 다른 값이고 서로 같지 않음. 그래서 심볼처럼 동작하지도 않고 그렇게 쓰면 안 됨

- 명명 인자의 기본값 문제에서는 Typst에 `none`과 함께 **`auto` 값**만 추가해도 원하는 거의 모든 명명 인자 인터페이스를 표현할 수 있음  
  `none`만으로는 대부분의 명명 인자 기본값으로 의미가 잘 맞지 않음. `none`은 기본 반환값으로는 좋지만 함수 인자로 들어가면 명사로서 올바른 의미를 담지 못하는 경우가 많음. `matrix(axes=None)`은 축을 제거한다는 뜻인지, 평소처럼 유지한다는 뜻인지 애매함. `none`을 넘기는 것과 아무것도 넘기지 않는 것이 다른지도 불명확함. 매개변수 포함 여부를 구분하려고 다중 디스패치로 가면, 그 매개변수의 동작을 문서화할 중심 위치를 잃게 됨  
  `auto`는 “가진 정보로 적절히 처리하라”는 뜻을 그대로 담는 훌륭한 기본값임. `auto | none` 시그니처는 더 명시적인 불리언처럼 쓸 수 있고, `T | auto | none`은 함수가 값을 어떻게 쓸지 꽤 많은 정보를 줌. 예를 들어 `T`가 `color`라면 `auto`는 흰색/검은색 같은 기본값을 고르거나 부모에서 상속할 가능성이 높고, `T`는 색을 명시적으로 설정하며, `none`은 맥락에 따라 색을 아예 설정하지 않거나 투명으로 처리할 수 있음

- 흥미롭고, 일부 패키지의 의미론이 어떻게 바뀔지 궁금함. 예를 들면 `Item | None`을 반환하는 대신 아래처럼 쓸 수 있음  
  ```py  
  NOT_FOUND = sentinel("NOT_FOUND")  
  def get_item(iid: str) -> Item | NOT_FOUND: ...  
  ```  
  물론 여러 센티널로 추가 의미를 담을 수도 있음. 원래도 가능했지만 문서에 “공식적으로 권장되는” 방식은 없었음. 이게 패키지 작성자들을 다른 방향으로 이끌 수도 있음  
  ```py  
  MISSING_ID = sentinel("MISSING_ID")  
  MISSING_VALUE = sentinel("MISSING_VALUE")
  
  def get_item(iid: str) -> Item | MISSING_ID | MISSING_VALUE: ...  
  ```  
  다소 억지 예시지만, 이 경우 기존 ID는 있으나 연결된 값이 없는 상황과 그런 ID 자체가 없어서 실패한 상황을 구분할 수 있음. “Python다운” 방식은 아마 예외를 쓰는 쪽이겠지만, 보통 Python을 작성할 때보다 더 **함수형 접근**처럼 보임
  - 예전에는 더미 클래스를 만들고 모듈별로 인스턴스화하던 **싱글턴**을 더 깔끔하게 쓰는 방식처럼 보임  
    ```py  
    class _MissingId: ...
    
    MISSING_ID = _MissingId()
    
    # elsewhere  
    from ... import MISSING_ID  
    ```  
    [Symbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)가 떠오름
  - PEP에서는 관련된 여러 센티널 값을 정의하거나 그 사이에 정렬 순서까지 둘 수 있다면, 대신 **Enum**이나 비슷한 것을 쓰라고 함

- 그냥 JavaScript의 **`Symbol` API**를 도입하는 편이 더 나았을 것 같음. 일반적으로도 쓸모가 있고, 여기서 해결하려는 문제도 같이 해결됨
