diff --git a/peps/pep-0661.rst b/peps/pep-0661.rst index 1d9e00917..c639ea5b4 100644 --- a/peps/pep-0661.rst +++ b/peps/pep-0661.rst @@ -33,11 +33,12 @@ programming. They have many uses, such as for: Python has the special value ``None``, which is intended to be used as such a sentinel value in most cases. However, sometimes an alternative sentinel -value is needed, usually when it needs to be distinct from ``None``. These -cases are common enough that several idioms for implementing such sentinels -have arisen over the years, but uncommon enough that there hasn't been a -clear need for standardization. However, the common implementations, -including some in the stdlib, suffer from several significant drawbacks. +value is needed, usually when it needs to be distinct from ``None`` since +``None`` is a valid value in that context. Such cases are common enough that +several idioms for implementing such sentinels have arisen over the years, but +uncommon enough that there hasn't been a clear need for standardization. +However, the common implementations, including some in the stdlib, suffer from +several significant drawbacks. This PEP proposes adding a utility for defining sentinel values, to be used in the stdlib and made publicly available as part of the stdlib. @@ -70,10 +71,10 @@ function's signature to be overly long and hard to read:: Additionally, two other drawbacks of many existing sentinels were brought up in the discussion: -1. Not having a distinct type, hence it being impossible to define clear - type signatures for functions with sentinels as default values -2. Incorrect behavior after being copied or unpickled, due to a separate - instance being created and thus comparisons using ``is`` failing +1. Some do not have a distinct type, hence it is impossible to define clear + type signatures for functions with such sentinels as default values. +2. They behave unexpectedly after being copied or unpickled, due to a separate + instance being created and thus comparisons using ``is`` failing. In the ensuing discussion, Victor Stinner supplied a list of currently used sentinel values in the Python standard library [2]_. This showed that the @@ -84,14 +85,15 @@ least one of the three above drawbacks. The discussion did not lead to any clear consensus on whether a standard implementation method is needed or desirable, whether the drawbacks mentioned are significant, nor which kind of implementation would be good. The author -of this PEP created an issue on bugs.python.org [3]_ suggesting options for -improvement, but that focused on only a single problematic aspect of a few -cases, and failed to gather any support. +of this PEP created an issue on bugs.python.org (now a GitHub issue [3]_) +suggesting options for improvement, but that focused on only a single +problematic aspect of a few cases, and failed to gather any support. A poll [4]_ was created on discuss.python.org to get a clearer sense of -the community's opinions. The poll's results were not conclusive, with 40% -voting for "The status-quo is fine / there’s no need for consistency in -this", but most voters voting for one or more standardized solutions. +the community's opinions. After nearly two weeks, significant further, +discussion, and 39 votes, the poll's results were not conclusive. 40% had +voted for "The status-quo is fine / there’s no need for consistency in +this", but most voters had voted for one or more standardized solutions. Specifically, 37% of the voters chose "Consistent use of a new, dedicated sentinel factory / class / meta-class, also made publicly available in the stdlib". @@ -142,8 +144,8 @@ Specification A new ``Sentinel`` class will be added to a new ``sentinels`` module. Its initializer will accept a single required argument, the name of the -sentinel object, and two optional arguments: the repr of the object, and the -name of its module:: +sentinel object, and three optional arguments: the repr of the object, its +boolean value, and the name of its module:: >>> from sentinels import Sentinel >>> NotGiven = Sentinel('NotGiven') @@ -160,7 +162,9 @@ operator, as is recommended for ``None``. Equality checks using ``==`` will also work as expected, returning ``True`` only when the object is compared with itself. Identity checks such as ``if value is MISSING:`` should usually be used rather than boolean checks such as ``if value:`` or ``if not value:``. -Sentinel instances are truthy by default, unlike ``None``. + +Sentinel instances are truthy by default, unlike ``None``. This parallels the +default for arbitrary classes, as well as the boolean value of ``Ellipsis``. The names of sentinels are unique within each module. When calling ``Sentinel()`` in a module where a sentinel with that name was already @@ -183,9 +187,12 @@ automatic recognition does not work as intended, such as perhaps when using Jython or IronPython. This parallels the designs of ``Enum`` and ``namedtuple``. For more details, see :pep:`435`. -The ``Sentinel`` class may be sub-classed. Instances of each sub-class will -be unique, even if using the same name and module. This allows for -customizing the behavior of sentinels, such as controlling their truthiness. +The ``Sentinel`` class may not be sub-classed, to avoid overly-clever uses +based on it, such as attempts to use it as a base for implementing singletons. +It is considered important that the addition of Sentinel to the stdlib should +add minimal complexity. + +Ordering comparisons are undefined for sentinel objects. Reference Implementation @@ -359,7 +366,22 @@ Additional Notes identical. If distinct sentinel objects are needed, make sure to use distinct names. -* There was a discussion on the typing-sig mailing list [8]_ about the typing +* There is no single desirable value for the "truthiness" of sentinels, i.e. + their boolean value. It is sometimes useful for the boolean value to be + ``True``, and sometimes ``False``. Of the built-in sentinels in Python, + ``None`` evaluates to ``False``, while ``Ellipsis`` (a.k.a. ``...``) + evaluates to ``True``. The desire for this to be set as needed came up in + discussions as well. + +* The boolean value of ``NotImplemented`` is ``True``, but using this is + deprecated since Python 3.9 (doing so generates a deprecation warning.) + This deprecation is due to issues specific to ``NotImplemented``, as + described in bpo-35712 [8]_. + +* To define multiple, related sentinel values, possibly with a defined + ordering among them, one should instead use ``Enum`` or something similar. + +* There was a discussion on the typing-sig mailing list [9]_ about the typing for these sentinels, where different options were discussed. @@ -368,12 +390,13 @@ References .. [1] Python-Dev mailing list: `The repr of a sentinel `_ .. [2] Python-Dev mailing list: `"The stdlib contains tons of sentinels" `_ -.. [3] `bpo-44123: Make function parameter sentinel values true singletons `_ +.. [3] `bpo-44123: Make function parameter sentinel values true singletons `_ .. [4] discuss.python.org Poll: `Sentinel Values in the Stdlib `_ .. [5] `The "sentinels" package on PyPI `_ .. [6] `The "sentinel" package on PyPI `_ .. [7] `Reference implementation at the taleinat/python-stdlib-sentinels GitHub repo `_ -.. [8] `Discussion thread about type signatures for these sentinels on the typing-sig mailing list `_ +.. [8] `bpo-35712: Make NotImplemented unusable in boolean context `_ +.. [9] `Discussion thread about type signatures for these sentinels on the typing-sig mailing list `_ Copyright