"Python 3.15 Sentinel vs object(): Which Default Is Better?"

"Every Python backend has a `_MISSING = object()` somewhere. Python 3.15's PEP 661 `sentinel()` builtin finally replaces it with readable reprs, copy/pickle-safe identity, and real type-hint support. This head-to-head comparison shows both options' strengths and weaknesses — and exactly when to…

Every Python backend has one of these buried somewhere: _MISSING = object() def fetch(url, *, timeout=_MISSING): if timeout is _MISSING: timeout = 30 ... It works — until it doesn&#x27;t. Print the signature and you get timeout=<object object at 0x7f8b2c1a3d40> garbage in your help() output. Deep-copy a config dict containing your sentinel and the is check silently breaks. Try to express "int or not-passed" in a type hint and mypy just shrugs. The idiom every Python developer learns is, frankly, a hack — and the question "python 3.15 sentinel vs object() default argument" is really asking whether PEP 661&#x27;s new sentinel() builtin finally kills it. Release-status disclaimer: The sentinel() builtin was tested on Python 3.15.0rc3 ; the 3.15.0 final is scheduled for 2026-10-09 . Can&#x27;t wait? The typing_extensions backport works on Python 3.14 and earlier today. Python 3.15 sentinel vs object() default argument: the choice Here&#x27;s the problem both options solve. Sometimes None is legitimate data — a user explicitly passing timeout=None to disable timeouts is different from not passing timeout at all. You need a third state: "no argument was passed." A *sentinel value* is a unique placeholder object whose only job is to signal that state, compared with is . The choice is between the 20-year-old idiom ( _MISSING = object() ) and the new dedicated builtin ( MISSING = sentinel("MISSING") ). One minimal example of each, then the full breakdown. Option A — the classic idiom: _MISSING = object() def get_user(user_id, default=_MISSING): if default is _MISSING: raise KeyError(f"no user {user_id}") return default import inspect print(inspect.signature(get_user)) # (user_id, default=<object object at 0x7f2c8b4d5e10>) — unreadable Option B — the 3.15 builtin: MISSING =…

Explore