Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ are always available. They are listed here in alphabetical order.
| | :func:`ascii` | | :func:`filter` | | :func:`map` | | **S** |
| | | | :func:`float` | | :func:`max` | | |func-set|_ |
| | **B** | | :func:`format` | | |func-memoryview|_ | | :func:`setattr` |
| | :func:`bin` | | |func-frozenset|_ | | :func:`min` | | :func:`slice` |
| | :func:`bool` | | | | | | :func:`sorted` |
| | :func:`breakpoint` | | **G** | | **N** | | :func:`staticmethod` |
| | |func-bytearray|_ | | :func:`getattr` | | :func:`next` | | |func-str|_ |
| | |func-bytes|_ | | :func:`globals` | | | | :func:`sum` |
| | | | | | **O** | | :func:`super` |
| | **C** | | **H** | | :func:`object` | | |
| | :func:`bin` | | |func-frozenset|_ | | :func:`min` | | :func:`sentinel` |
| | :func:`bool` | | | | | | :func:`slice` |
| | :func:`breakpoint` | | **G** | | **N** | | :func:`sorted` |
| | |func-bytearray|_ | | :func:`getattr` | | :func:`next` | | :func:`staticmethod` |
| | |func-bytes|_ | | :func:`globals` | | | | |func-str|_ |
| | | | | | **O** | | :func:`sum` |
| | **C** | | **H** | | :func:`object` | | :func:`super` |
| | :func:`callable` | | :func:`hasattr` | | :func:`oct` | | **T** |
| | :func:`chr` | | :func:`hash` | | :func:`open` | | |func-tuple|_ |
| | :func:`classmethod` | | :func:`help` | | :func:`ord` | | :func:`type` |
Expand Down Expand Up @@ -1827,6 +1827,40 @@ are always available. They are listed here in alphabetical order.
:func:`setattr`.


.. class:: sentinel(name, /)

Return a new unique sentinel object. *name* must be a :class:`str`, and is
used as the returned object's representation::
Comment thread
JelleZijlstra marked this conversation as resolved.

>>> MISSING = sentinel("MISSING")
>>> MISSING
MISSING

Sentinel objects are truthy and compare equal only to themselves. They are
intended to be compared with the ``is`` operator.
Comment thread
JelleZijlstra marked this conversation as resolved.
Outdated

Shallow and deep copies of a sentinel object return the object itself.

Sentinels importable from their defining module by name preserve their
identity when pickled and unpickled. Sentinels that are not importable by
module and name are not picklable.

Sentinel objects support the ``|`` operator for use in type expressions::
Comment thread
JelleZijlstra marked this conversation as resolved.
Outdated

def next_value(default: int | MISSING = MISSING):
...

.. attribute:: __name__

The sentinel's name.

.. attribute:: __module__

The name of the module where the sentinel was created.

.. versionadded:: next


.. class:: slice(stop, /)
slice(start, stop, step=None, /)

Expand Down
14 changes: 14 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ Summary -- Release highlights
<whatsnew315-lazy-imports>`
* :pep:`814`: :ref:`Add frozendict built-in type
<whatsnew315-frozendict>`
* :pep:`661`: :ref:`Add sentinel built-in type
<whatsnew315-sentinel>`
* :pep:`799`: :ref:`A dedicated profiling package for organizing Python
profiling tools <whatsnew315-profiling-package>`
* :pep:`799`: :ref:`Tachyon: High frequency statistical sampling profiler
Expand Down Expand Up @@ -234,6 +236,18 @@ to accept also other mapping types such as :class:`~types.MappingProxyType`.
(Contributed by Victor Stinner and Donghee Na in :gh:`141510`.)


.. _whatsnew315-sentinel:

:pep:`661`: Add sentinel built-in type
--------------------------------------

A new :class:`sentinel` type is added to the :mod:`builtins` module for
creating unique sentinel values with a concise representation. Sentinel
objects preserve identity when copied, support use in type expressions with
the ``|`` operator, and can be pickled when they are importable by module and
name.


.. _whatsnew315-profiling-package:

:pep:`799`: A dedicated profiling package
Expand Down
19 changes: 19 additions & 0 deletions Include/internal/pycore_sentinelobject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Sentinel object interface.

#ifndef Py_INTERNAL_SENTINELOBJECT_H
#define Py_INTERNAL_SENTINELOBJECT_H
#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif

extern PyTypeObject PySentinel_Type;
#define _PySentinel_Check(op) Py_IS_TYPE((op), &PySentinel_Type)

#ifdef __cplusplus
}
#endif
#endif // !Py_INTERNAL_SENTINELOBJECT_H
62 changes: 62 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import builtins
import collections
import contextlib
import copy
import decimal
import fractions
import gc
Expand Down Expand Up @@ -52,6 +53,10 @@

# used as proof of globals being used
A_GLOBAL_VALUE = 123
A_SENTINEL = sentinel("A_SENTINEL")

class SentinelContainer:
CLASS_SENTINEL = sentinel("SentinelContainer.CLASS_SENTINEL")

class Squares:

Expand Down Expand Up @@ -1903,6 +1908,63 @@ class C:
__repr__ = None
self.assertRaises(TypeError, repr, C())

def test_sentinel(self):
missing = sentinel("MISSING")
other = sentinel("MISSING")

self.assertIsInstance(missing, sentinel)
self.assertIs(type(missing), sentinel)
self.assertEqual(missing.__name__, "MISSING")
self.assertEqual(missing.__module__, __name__)
self.assertIsNot(missing, other)
self.assertEqual(repr(missing), "MISSING")
self.assertTrue(missing)
self.assertIs(copy.copy(missing), missing)
self.assertIs(copy.deepcopy(missing), missing)
self.assertEqual(missing, missing)
self.assertNotEqual(missing, other)
self.assertRaises(TypeError, sentinel)
self.assertRaises(TypeError, sentinel, "MISSING", "EXTRA")
self.assertRaises(TypeError, sentinel, name="MISSING")
with self.assertRaisesRegex(TypeError, "must be str"):
sentinel(1)
self.assertTrue(sentinel.__flags__ & support._TPFLAGS_IMMUTABLETYPE)
self.assertFalse(sentinel.__flags__ & support._TPFLAGS_BASETYPE)
with self.assertRaises(TypeError):
class SubSentinel(sentinel):
pass
with self.assertRaises(TypeError):
sentinel.attribute = "value"
with self.assertRaises(AttributeError):
missing.__name__ = "CHANGED"
with self.assertRaises(AttributeError):
missing.__module__ = "changed"

def test_sentinel_pickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.subTest(protocol=proto):
self.assertIs(
pickle.loads(pickle.dumps(A_SENTINEL, protocol=proto)),
A_SENTINEL)
self.assertIs(
pickle.loads(pickle.dumps(
SentinelContainer.CLASS_SENTINEL, protocol=proto)),
SentinelContainer.CLASS_SENTINEL)

missing = sentinel("MISSING")
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.subTest(protocol=proto):
with self.assertRaises(pickle.PicklingError):
pickle.dumps(missing, protocol=proto)

def test_sentinel_union(self):
missing = sentinel("MISSING")

self.assertEqual((missing | int).__args__, (missing, int))
self.assertEqual((int | missing).__args__, (int, missing))
self.assertIs(missing | missing, missing)
self.assertEqual(repr(int | missing), "int | MISSING")

def test_round(self):
self.assertEqual(round(0.0), 0.0)
self.assertEqual(type(round(0.0)), int)
Expand Down
1 change: 1 addition & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ OBJECT_OBJS= \
Objects/picklebufobject.o \
Objects/rangeobject.o \
Objects/setobject.o \
Objects/sentinelobject.o \
Objects/sliceobject.o \
Objects/structseq.o \
Objects/templateobject.o \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the :class:`sentinel` built-in class for creating unique sentinel values.
34 changes: 34 additions & 0 deletions Objects/clinic/sentinelobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "pycore_pyerrors.h" // _PyErr_Occurred()
#include "pycore_pymem.h" // _PyMem_IsPtrFreed()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_sentinelobject.h" // PySentinel_Type
#include "pycore_symtable.h" // PySTEntry_Type
#include "pycore_template.h" // _PyTemplate_Type _PyTemplateIter_Type
#include "pycore_tuple.h" // _PyTuple_DebugMallocStats()
Expand Down Expand Up @@ -2600,6 +2601,7 @@ static PyTypeObject* static_types[] = {
&PySeqIter_Type,
&PySetIter_Type,
&PySet_Type,
&PySentinel_Type,
&PySlice_Type,
&PyStdPrinter_Type,
&PySuper_Type,
Expand Down
Loading
Loading