| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 0efd679 commit 29a92ab
24 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -112,6 +112,7 @@ Other Objects | |||
| 112 | 112 | picklebuffer.rst | |
| 113 | 113 | weakref.rst | |
| 114 | 114 | capsule.rst | |
| 115 | + sentinel.rst | ||
| 115 | 116 | frame.rst | |
| 116 | 117 | gen.rst | |
| 117 | 118 | coro.rst | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,35 @@ | |||
| 1 | + .. highlight:: c | ||
| 2 | + | ||
| 3 | + .. _sentinelobjects: | ||
| 4 | + | ||
| 5 | + Sentinel objects | ||
| 6 | + ---------------- | ||
| 7 | + | ||
| 8 | + .. c:var:: PyTypeObject PySentinel_Type | ||
| 9 | + | ||
| 10 | + This instance of :c:type:`PyTypeObject` represents the Python | ||
| 11 | + :class:`sentinel` type. This is the same object as :class:`sentinel`. | ||
| 12 | + | ||
| 13 | + .. versionadded:: next | ||
| 14 | + | ||
| 15 | + .. c:function:: int PySentinel_Check(PyObject *o) | ||
| 16 | + | ||
| 17 | + Return true if *o* is a :class:`sentinel` object. The :class:`sentinel` type | ||
| 18 | + does not allow subclasses, so this check is exact. | ||
| 19 | + | ||
| 20 | + .. versionadded:: next | ||
| 21 | + | ||
| 22 | + .. c:function:: PyObject* PySentinel_New(const char *name, const char *module_name) | ||
| 23 | + | ||
| 24 | + Return a new :class:`sentinel` object with :attr:`~sentinel.__name__` set to | ||
| 25 | + *name* and :attr:`~sentinel.__module__` set to *module_name*. | ||
| 26 | + *name* must not be ``NULL``. If *module_name* is ``NULL``, :attr:`~sentinel.__module__` | ||
| 27 | + is set to ``None``. | ||
| 28 | + Return ``NULL`` with an exception set on failure. | ||
| 29 | + | ||
| 30 | + For pickling to work, *module_name* must be the name of an importable | ||
| 31 | + module, and the sentinel must be accessible from that module under a | ||
| 32 | + path matching *name*. Pickle treats *name* as a global variable name | ||
| 33 | + in *module_name* (see :meth:`object.__reduce__`). | ||
| 34 | + | ||
| 35 | + .. versionadded:: next | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2037,6 +2037,10 @@ PySeqIter_Check:PyObject *:op:0: | |||
| 2037 | 2037 | PySeqIter_New:PyObject*::+1: | |
| 2038 | 2038 | PySeqIter_New:PyObject*:seq:0: | |
| 2039 | 2039 | ||
| 2040 | + PySentinel_New:PyObject*::+1: | ||
| 2041 | + PySentinel_New:const char*:name:: | ||
| 2042 | + PySentinel_New:const char*:module_name:: | ||
| 2043 | + | ||
| 2040 | 2044 | PySequence_Check:int::: | |
| 2041 | 2045 | PySequence_Check:PyObject*:o:0: | |
| 2042 | 2046 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -19,13 +19,13 @@ are always available. They are listed here in alphabetical order. | |||
| 19 | 19 | | | :func:`ascii` | | :func:`filter` | | :func:`map` | | **S** | | |
| 20 | 20 | | | | | :func:`float` | | :func:`max` | | |func-set|_ | | |
| 21 | 21 | | | **B** | | :func:`format` | | |func-memoryview|_ | | :func:`setattr` | | |
| 22 | - | | :func:`bin` | | |func-frozenset|_ | | :func:`min` | | :func:`slice` | | ||
| 23 | - | | :func:`bool` | | | | | | :func:`sorted` | | ||
| 24 | - | | :func:`breakpoint` | | **G** | | **N** | | :func:`staticmethod` | | ||
| 25 | - | | |func-bytearray|_ | | :func:`getattr` | | :func:`next` | | |func-str|_ | | ||
| 26 | - | | |func-bytes|_ | | :func:`globals` | | | | :func:`sum` | | ||
| 27 | - | | | | | | **O** | | :func:`super` | | ||
| 28 | - | | **C** | | **H** | | :func:`object` | | | | ||
| 22 | + | | :func:`bin` | | |func-frozenset|_ | | :func:`min` | | :func:`sentinel` | | ||
| 23 | + | | :func:`bool` | | | | | | :func:`slice` | | ||
| 24 | + | | :func:`breakpoint` | | **G** | | **N** | | :func:`sorted` | | ||
| 25 | + | | |func-bytearray|_ | | :func:`getattr` | | :func:`next` | | :func:`staticmethod` | | ||
| 26 | + | | |func-bytes|_ | | :func:`globals` | | | | |func-str|_ | | ||
| 27 | + | | | | | | **O** | | :func:`sum` | | ||
| 28 | + | | **C** | | **H** | | :func:`object` | | :func:`super` | | ||
| 29 | 29 | | | :func:`callable` | | :func:`hasattr` | | :func:`oct` | | **T** | | |
| 30 | 30 | | | :func:`chr` | | :func:`hash` | | :func:`open` | | |func-tuple|_ | | |
| 31 | 31 | | | :func:`classmethod` | | :func:`help` | | :func:`ord` | | :func:`type` | | |
@@ -1827,6 +1827,61 @@ are always available. They are listed here in alphabetical order. | |||
| 1827 | 1827 | :func:`setattr`. | |
| 1828 | 1828 | ||
| 1829 | 1829 | ||
| 1830 | + .. class:: sentinel(name, /) | ||
| 1831 | + | ||
| 1832 | + Return a new unique sentinel object. *name* must be a :class:`str`, and is | ||
| 1833 | + used as the returned object's representation:: | ||
| 1834 | + | ||
| 1835 | + >>> MISSING = sentinel("MISSING") | ||
| 1836 | + >>> MISSING | ||
| 1837 | + MISSING | ||
| 1838 | + | ||
| 1839 | + Sentinel objects are truthy and compare equal only to themselves. They are | ||
| 1840 | + intended to be compared with the :keyword:`is` operator. | ||
| 1841 | + | ||
| 1842 | + Shallow and deep copies of a sentinel object return the object itself. | ||
| 1843 | + | ||
| 1844 | + Sentinels are conventionally assigned to a variable with a matching name. | ||
| 1845 | + Sentinels defined in this way can be used in :term:`type hints <type hint>`:: | ||
| 1846 | + | ||
| 1847 | + MISSING = sentinel("MISSING") | ||
| 1848 | + | ||
| 1849 | + def next_value(default: int | MISSING = MISSING): | ||
| 1850 | + ... | ||
| 1851 | + | ||
| 1852 | + Sentinel objects support the :ref:`| <bitwise>` operator for use in type expressions. | ||
| 1853 | + | ||
| 1854 | + :mod:`Pickling <pickle>` is supported for sentinel objects that are | ||
| 1855 | + placed in the global scope of a module under a name matching the sentinel's | ||
| 1856 | + name, and for sentinels placed in class scopes with a name matching the | ||
| 1857 | + :term:`qualified name` of the sentinel. Other sentinels, such as those | ||
| 1858 | + defined in a function scope, are not picklable. The identity of the sentinel is preserved | ||
| 1859 | + after pickling:: | ||
| 1860 | + | ||
| 1861 | + import pickle | ||
| 1862 | + | ||
| 1863 | + PICKLABLE = sentinel("PICKLABLE") | ||
| 1864 | + | ||
| 1865 | + assert pickle.loads(pickle.dumps(PICKLABLE)) is PICKLABLE | ||
| 1866 | + | ||
| 1867 | + class Cls: | ||
| 1868 | + PICKLABLE = sentinel("Cls.PICKLABLE") | ||
| 1869 | + | ||
| 1870 | + assert pickle.loads(pickle.dumps(Cls.PICKLABLE)) is Cls.PICKLABLE | ||
| 1871 | + | ||
| 1872 | + Sentinel objects have the following attributes: | ||
| 1873 | + | ||
| 1874 | + .. attribute:: __name__ | ||
| 1875 | + | ||
| 1876 | + The sentinel's name. | ||
| 1877 | + | ||
| 1878 | + .. attribute:: __module__ | ||
| 1879 | + | ||
| 1880 | + The name of the module where the sentinel was created. | ||
| 1881 | + | ||
| 1882 | + .. versionadded:: next | ||
| 1883 | + | ||
| 1884 | + | ||
| 1830 | 1885 | .. class:: slice(stop, /) | |
| 1831 | 1886 | slice(start, stop, step=None, /) | |
| 1832 | 1887 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -69,6 +69,8 @@ Summary -- Release highlights | |||
| 69 | 69 | <whatsnew315-lazy-imports>` | |
| 70 | 70 | * :pep:`814`: :ref:`Add frozendict built-in type | |
| 71 | 71 | <whatsnew315-frozendict>` | |
| 72 | + * :pep:`661`: :ref:`Add sentinel built-in type | ||
| 73 | + <whatsnew315-sentinel>` | ||
| 72 | 74 | * :pep:`799`: :ref:`A dedicated profiling package for organizing Python | |
| 73 | 75 | profiling tools <whatsnew315-profiling-package>` | |
| 74 | 76 | * :pep:`799`: :ref:`Tachyon: High frequency statistical sampling profiler | |
@@ -247,6 +249,20 @@ to accept also other mapping types such as :class:`~types.MappingProxyType`. | |||
| 247 | 249 | (Contributed by Victor Stinner and Donghee Na in :gh:`141510`.) | |
| 248 | 250 | ||
| 249 | 251 | ||
| 252 | + .. _whatsnew315-sentinel: | ||
| 253 | + | ||
| 254 | + :pep:`661`: Add sentinel built-in type | ||
| 255 | + -------------------------------------- | ||
| 256 | + | ||
| 257 | + A new :class:`sentinel` type is added to the :mod:`builtins` module for | ||
| 258 | + creating unique sentinel values with a concise representation. Sentinel | ||
| 259 | + objects preserve identity when copied, support use in type expressions with | ||
| 260 | + the ``|`` operator, and can be pickled when they are importable by module and | ||
| 261 | + name. | ||
| 262 | + | ||
| 263 | + (PEP by Tal Einat; contributed by Jelle Zijlstra in :gh:`148829`.) | ||
| 264 | + | ||
| 265 | + | ||
| 250 | 266 | .. _whatsnew315-profiling-package: | |
| 251 | 267 | ||
| 252 | 268 | :pep:`799`: A dedicated profiling package | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -117,6 +117,7 @@ __pragma(warning(disable: 4201)) | |||
| 117 | 117 | #include "cpython/genobject.h" | |
| 118 | 118 | #include "descrobject.h" | |
| 119 | 119 | #include "genericaliasobject.h" | |
| 120 | + #include "sentinelobject.h" | ||
| 120 | 121 | #include "warnings.h" | |
| 121 | 122 | #include "weakrefobject.h" | |
| 122 | 123 | #include "structseq.h" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,22 @@ | |||
| 1 | + /* Sentinel object interface */ | ||
| 2 | + | ||
| 3 | + #ifndef Py_SENTINELOBJECT_H | ||
| 4 | + #define Py_SENTINELOBJECT_H | ||
| 5 | + #ifdef __cplusplus | ||
| 6 | + extern "C" { | ||
| 7 | + #endif | ||
| 8 | + | ||
| 9 | + #ifndef Py_LIMITED_API | ||
| 10 | + PyAPI_DATA(PyTypeObject) PySentinel_Type; | ||
| 11 | + | ||
| 12 | + #define PySentinel_Check(op) Py_IS_TYPE((op), &PySentinel_Type) | ||
| 13 | + | ||
| 14 | + PyAPI_FUNC(PyObject *) PySentinel_New( | ||
| 15 | + const char *name, | ||
| 16 | + const char *module_name); | ||
| 17 | + #endif | ||
| 18 | + | ||
| 19 | + #ifdef __cplusplus | ||
| 20 | + } | ||
| 21 | + #endif | ||
| 22 | + #endif /* !Py_SENTINELOBJECT_H */ | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3244,6 +3244,7 @@ def test_builtin_types(self): | |||
| 3244 | 3244 | 'BuiltinImporter': (3, 3), | |
| 3245 | 3245 | 'str': (3, 4), # not interoperable with Python < 3.4 | |
| 3246 | 3246 | 'frozendict': (3, 15), | |
| 3247 | + 'sentinel': (3, 15), | ||
| 3247 | 3248 | } | |
| 3248 | 3249 | for t in builtins.__dict__.values(): | |
| 3249 | 3250 | if isinstance(t, type) and not issubclass(t, BaseException): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,6 +4,7 @@ | |||
| 4 | 4 | import builtins | |
| 5 | 5 | import collections | |
| 6 | 6 | import contextlib | |
| 7 | + import copy | ||
| 7 | 8 | import decimal | |
| 8 | 9 | import fractions | |
| 9 | 10 | import gc | |
@@ -21,6 +22,7 @@ | |||
| 21 | 22 | import typing | |
| 22 | 23 | import unittest | |
| 23 | 24 | import warnings | |
| 25 | + import weakref | ||
| 24 | 26 | from contextlib import ExitStack | |
| 25 | 27 | from functools import partial | |
| 26 | 28 | from inspect import CO_COROUTINE | |
@@ -52,6 +54,10 @@ | |||
| 52 | 54 | ||
| 53 | 55 | # used as proof of globals being used | |
| 54 | 56 | A_GLOBAL_VALUE = 123 | |
| 57 | + A_SENTINEL = sentinel("A_SENTINEL") | ||
| 58 | + | ||
| 59 | + class SentinelContainer: | ||
| 60 | + CLASS_SENTINEL = sentinel("SentinelContainer.CLASS_SENTINEL") | ||
| 55 | 61 | ||
| 56 | 62 | class Squares: | |
| 57 | 63 | ||
@@ -1903,6 +1909,98 @@ class C: | |||
| 1903 | 1909 | __repr__ = None | |
| 1904 | 1910 | self.assertRaises(TypeError, repr, C()) | |
| 1905 | 1911 | ||
| 1912 | + def test_sentinel(self): | ||
| 1913 | + missing = sentinel("MISSING") | ||
| 1914 | + other = sentinel("MISSING") | ||
| 1915 | + | ||
| 1916 | + self.assertIsInstance(missing, sentinel) | ||
| 1917 | + self.assertIs(type(missing), sentinel) | ||
| 1918 | + self.assertEqual(missing.__name__, "MISSING") | ||
| 1919 | + self.assertEqual(missing.__module__, __name__) | ||
| 1920 | + self.assertIsNot(missing, other) | ||
| 1921 | + self.assertEqual(repr(missing), "MISSING") | ||
| 1922 | + self.assertTrue(missing) | ||
| 1923 | + self.assertIs(copy.copy(missing), missing) | ||
| 1924 | + self.assertIs(copy.deepcopy(missing), missing) | ||
| 1925 | + self.assertEqual(missing, missing) | ||
| 1926 | + self.assertNotEqual(missing, other) | ||
| 1927 | + self.assertRaises(TypeError, sentinel) | ||
| 1928 | + self.assertRaises(TypeError, sentinel, "MISSING", "EXTRA") | ||
| 1929 | + self.assertRaises(TypeError, sentinel, name="MISSING") | ||
| 1930 | + with self.assertRaisesRegex(TypeError, "must be str"): | ||
| 1931 | + sentinel(1) | ||
| 1932 | + self.assertTrue(sentinel.__flags__ & support._TPFLAGS_IMMUTABLETYPE) | ||
| 1933 | + self.assertTrue(sentinel.__flags__ & support._TPFLAGS_HAVE_GC) | ||
| 1934 | + self.assertFalse(sentinel.__flags__ & support._TPFLAGS_BASETYPE) | ||
| 1935 | + with self.assertRaises(TypeError): | ||
| 1936 | + class SubSentinel(sentinel): | ||
| 1937 | + pass | ||
| 1938 | + with self.assertRaises(TypeError): | ||
| 1939 | + sentinel.attribute = "value" | ||
| 1940 | + with self.assertRaises(AttributeError): | ||
| 1941 | + missing.__name__ = "CHANGED" | ||
| 1942 | + with self.assertRaises(AttributeError): | ||
| 1943 | + missing.__module__ = "changed" | ||
| 1944 | + with self.assertRaises(AttributeError): | ||
| 1945 | + del missing.__name__ | ||
| 1946 | + with self.assertRaises(AttributeError): | ||
| 1947 | + del missing.__module__ | ||
| 1948 | + | ||
| 1949 | + def test_sentinel_pickle(self): | ||
| 1950 | + for proto in range(pickle.HIGHEST_PROTOCOL + 1): | ||
| 1951 | + with self.subTest(protocol=proto): | ||
| 1952 | + self.assertIs( | ||
| 1953 | + pickle.loads(pickle.dumps(A_SENTINEL, protocol=proto)), | ||
| 1954 | + A_SENTINEL) | ||
| 1955 | + self.assertIs( | ||
| 1956 | + pickle.loads(pickle.dumps( | ||
| 1957 | + SentinelContainer.CLASS_SENTINEL, protocol=proto)), | ||
| 1958 | + SentinelContainer.CLASS_SENTINEL) | ||
| 1959 | + | ||
| 1960 | + missing = sentinel("MISSING") | ||
| 1961 | + for proto in range(pickle.HIGHEST_PROTOCOL + 1): | ||
| 1962 | + with self.subTest(protocol=proto): | ||
| 1963 | + with self.assertRaises(pickle.PicklingError): | ||
| 1964 | + pickle.dumps(missing, protocol=proto) | ||
| 1965 | + | ||
| 1966 | + def test_sentinel_str_subclass_name_cycle(self): | ||
| 1967 | + class Name(str): | ||
| 1968 | + pass | ||
| 1969 | + | ||
| 1970 | + name = Name("MISSING") | ||
| 1971 | + missing = sentinel(name) | ||
| 1972 | + self.assertIs(missing.__name__, name) | ||
| 1973 | + self.assertTrue(gc.is_tracked(missing)) | ||
| 1974 | + | ||
| 1975 | + name.missing = missing | ||
| 1976 | + ref = weakref.ref(name) | ||
| 1977 | + del name, missing | ||
| 1978 | + support.gc_collect() | ||
| 1979 | + self.assertIsNone(ref()) | ||
| 1980 | + | ||
| 1981 | + def test_sentinel_union(self): | ||
| 1982 | + missing = sentinel("MISSING") | ||
| 1983 | + | ||
| 1984 | + self.assertIsInstance(missing | int, typing.Union) | ||
| 1985 | + self.assertEqual((missing | int).__args__, (missing, int)) | ||
| 1986 | + self.assertIsInstance(int | missing, typing.Union) | ||
| 1987 | + self.assertEqual((int | missing).__args__, (int, missing)) | ||
| 1988 | + self.assertIs(missing | missing, missing) | ||
| 1989 | + self.assertEqual(repr(int | missing), "int | MISSING") | ||
| 1990 | + self.assertIsInstance(missing | None, typing.Union) | ||
| 1991 | + self.assertEqual((missing | None).__args__, (missing, type(None))) | ||
| 1992 | + self.assertIsInstance(None | missing, typing.Union) | ||
| 1993 | + self.assertEqual((None | missing).__args__, (type(None), missing)) | ||
| 1994 | + self.assertIsInstance(missing | list[int], typing.Union) | ||
| 1995 | + self.assertEqual((missing | list[int]).__args__, (missing, list[int])) | ||
| 1996 | + self.assertIsInstance(missing | (int | str), typing.Union) | ||
| 1997 | + self.assertEqual((missing | (int | str)).__args__, (missing, int, str)) | ||
| 1998 | + | ||
| 1999 | + with self.assertRaises(TypeError): | ||
| 2000 | + missing | 1 | ||
| 2001 | + with self.assertRaises(TypeError): | ||
| 2002 | + 1 | missing | ||
| 2003 | + | ||
| 1906 | 2004 | def test_round(self): | |
| 1907 | 2005 | self.assertEqual(round(0.0), 0.0) | |
| 1908 | 2006 | self.assertEqual(type(round(0.0)), int) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,6 @@ | |||
| 1 | 1 | import enum | |
| 2 | 2 | import os | |
| 3 | + import pickle | ||
| 3 | 4 | import sys | |
| 4 | 5 | import textwrap | |
| 5 | 6 | import unittest | |
@@ -63,6 +64,27 @@ def test_get_constant_borrowed(self): | |||
| 63 | 64 | self.check_get_constant(_testlimitedcapi.get_constant_borrowed) | |
| 64 | 65 | ||
| 65 | 66 | ||
| 67 | + class SentinelTest(unittest.TestCase): | ||
| 68 | + | ||
| 69 | + def test_pysentinel_new(self): | ||
| 70 | + marker = _testcapi.pysentinel_new("CAPI_SENTINEL", __name__) | ||
| 71 | + self.assertIs(type(marker), sentinel) | ||
| 72 | + self.assertTrue(_testcapi.pysentinel_check(marker)) | ||
| 73 | + self.assertFalse(_testcapi.pysentinel_check(object())) | ||
| 74 | + self.assertEqual(marker.__name__, "CAPI_SENTINEL") | ||
| 75 | + self.assertEqual(marker.__module__, __name__) | ||
| 76 | + self.assertEqual(repr(marker), "CAPI_SENTINEL") | ||
| 77 | + | ||
| 78 | + no_module = _testcapi.pysentinel_new("NO_MODULE") | ||
| 79 | + self.assertIs(type(no_module), sentinel) | ||
| 80 | + self.assertEqual(no_module.__name__, "NO_MODULE") | ||
| 81 | + self.assertIs(no_module.__module__, None) | ||
| 82 | + | ||
| 83 | + globals()["CAPI_SENTINEL"] = marker | ||
| 84 | + self.addCleanup(globals().pop, "CAPI_SENTINEL", None) | ||
| 85 | + self.assertIs(pickle.loads(pickle.dumps(marker)), marker) | ||
| 86 | + | ||
| 87 | + | ||
| 66 | 88 | class PrintTest(unittest.TestCase): | |
| 67 | 89 | def testPyObjectPrintObject(self): | |
| 68 | 90 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments