Skip to content

Commit

Permalink
PEP 661: specify comparisons and how to write type annotations (#2094)
Browse files Browse the repository at this point in the history
  • Loading branch information
taleinat authored Sep 30, 2021
1 parent 1c8a95f commit d3b653a
Showing 1 changed file with 38 additions and 14 deletions.
52 changes: 38 additions & 14 deletions pep-0661.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ TL;DR: See the `Specification`_ and `Reference Implementation`_.
Abstract
========

Unique placeholder values, widely known as "sentinel values", are useful in
Unique placeholder values, commonly known as "sentinel values", are useful in
Python programs for several things, such as default values for function
arguments where ``None`` is a valid input value. These cases are common
enough for several idioms for implementing such "sentinels" to have arisen
Expand Down Expand Up @@ -120,13 +120,27 @@ and a single optional argument, the repr of the object.
>>> MISSING
mymodule.MISSING

Checking if a value is such a sentinel *should* be done using the ``is``
operator, as is recommended for ``None``. Equality checks using ``==`` will
also work as expected, returning ``True`` only when the object is compared
with itself.

A third optional argument, the name of the module where the sentinel is
defined, exists to be used to support cases where the name of the module
cannot be found by inspecting the stack frame. This is identical to the
pattern used by ``collections.namedtuple``. (The name of the module is
used to choose a unique name for the class generated for the new sentinel,
which is set as an attribute of the ``sentinels`` module.)
The name should be set to the name of the variable used to reference the
object, as in the examples above. Otherwise, the sentinel object won't be
able to survive copying or pickling+unpickling while retaining the above
described behavior. Note, that when defined in a class scope, the name must
be the fully-qualified name of the variable in the module, for example::

class MyClass:
NotGiven = sentinel('MyClass.NotGiven')

Type annotations for sentinel values will use `typing.Literal`_.
For example::

def foo(value: int | Literal[NotGiven]) -> None:
...

.. _typing.Literal: https://docs.python.org/3/library/typing.html#typing.Literal


Reference Implementation
Expand All @@ -145,11 +159,11 @@ The reference implementation is found in a dedicated GitHub repo
'__repr__': lambda self: repr,
}
cls = type(class_name, (), class_namespace)
cls.__module__ = __name__
globals()[class_name] = cls
cls.__module__ = module
_get_parent_frame().f_globals[class_name] = cls

sentinel = cls()
cls.__new__ = lambda cls: sentinel
cls.__new__ = lambda cls_: sentinel

return sentinel

Expand All @@ -159,6 +173,15 @@ The reference implementation is found in a dedicated GitHub repo
sentinel_qualname.replace('.', '_')])


Note that a dedicated class is created automatically for each sentinel object.
This class is assigned to the namespace of the module from which the
``sentinel()`` call was made, or to that of the ``sentinels`` module itself as
a fallback. These classes have long names comprised of several parts to
ensure their uniqueness. However, these names usually wouldn't be used, since
type annotations should use ``Literal[]`` as described above, and identity
checks should be preferred over type checks.


Rejected Ideas
==============

Expand Down Expand Up @@ -256,10 +279,10 @@ To have a clear repr, one could define ``__repr__``:
class NotGiven(metaclass=SentinelMeta): pass

However, all such implementations don't have a dedicated type for the
sentinel, which is considered desirable. A dedicated type could be created
by a meta-class or class decorator, but at that point the implementation would
become much more complex and loses its advantages over the chosen
implementation.
sentinel, which is considered desirable for strict typing. A dedicated type
could be created by a meta-class or class decorator, but at that point the
implementation would become much more complex and loses its advantages over
the chosen implementation.

Additionally, using classes this way is unusual and could be confusing.

Expand Down Expand Up @@ -300,6 +323,7 @@ References
.. [5] `bpo-44123: Make function parameter sentinel values true singletons <https://bugs.python.org/issue44123>`_
.. [6] `The "sentinels" package on PyPI <https://pypi.org/project/sentinels/>`_
.. [7] `The "sentinel" package on PyPI <https://pypi.org/project/sentinel/>`_
.. [8] `Discussion thread about type signatures for these sentinels on the typing-sig mailing list <https://mail.python.org/archives/list/typing-sig@python.org/thread/NDEJ7UCDPINP634GXWDARVMTGDVSNBKV/#LVCPTY26JQJW7NKGKGAZXHQKWVW7GOGL>`_
Copyright
Expand Down

0 comments on commit d3b653a

Please sign in to comment.