Skip to content

Commit d386fff

Browse files
authored
PEP 661: Some changes after submission (before SC review) (#4232)
* Use "sentinellib" for the new module name This avoids breaking code using the existing "sentinels" PyPI package. * Change type annotations to use the bare name rather than Literal * Use sys._getframemodulename() instead of sys._getframe() in inline reference impl * Simplify, dropping support for custom repr and setting truthiness * Tweak some wording * A few more words about the advantages of the bare name for type signatures * Correct __reduce__ in the reference impl * Use the fully qualified name for the repr even when defined in a class * Remove custom repr in the example for the sentinel decorator suggestion
1 parent 24e1e4a commit d386fff

File tree

1 file changed

+49
-60
lines changed

1 file changed

+49
-60
lines changed

peps/pep-0661.rst

Lines changed: 49 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -142,52 +142,42 @@ all of these criteria (see `Reference Implementation`_).
142142
Specification
143143
=============
144144

145-
A new ``Sentinel`` class will be added to a new ``sentinels`` module.
146-
Its initializer will accept a single required argument, the name of the
147-
sentinel object, and three optional arguments: the repr of the object, its
148-
boolean value, and the name of its module::
149-
150-
>>> from sentinels import Sentinel
151-
>>> NotGiven = Sentinel('NotGiven')
152-
>>> NotGiven
153-
<NotGiven>
154-
>>> MISSING = Sentinel('MISSING', repr='mymodule.MISSING')
145+
A new ``Sentinel`` class will be added to a new ``sentinellib`` module.
146+
147+
>>> from sentinellib import Sentinel
148+
>>> MISSING = Sentinel('MISSING')
155149
>>> MISSING
156-
mymodule.MISSING
157-
>>> MEGA = Sentinel('MEGA',
158-
repr='<MEGA>',
159-
bool_value=False,
160-
module_name='mymodule')
161-
<MEGA>
150+
MISSING
162151

163152
Checking if a value is such a sentinel *should* be done using the ``is``
164153
operator, as is recommended for ``None``. Equality checks using ``==`` will
165154
also work as expected, returning ``True`` only when the object is compared
166155
with itself. Identity checks such as ``if value is MISSING:`` should usually
167156
be used rather than boolean checks such as ``if value:`` or ``if not value:``.
168157

169-
Sentinel instances are truthy by default, unlike ``None``. This parallels the
170-
default for arbitrary classes, as well as the boolean value of ``Ellipsis``.
158+
Sentinel instances are "truthy", i.e. boolean evaluation will result in
159+
``True``. This parallels the default for arbitrary classes, as well as the
160+
boolean value of ``Ellipsis``. This is unlike ``None``, which is "falsy".
171161

172162
The names of sentinels are unique within each module. When calling
173163
``Sentinel()`` in a module where a sentinel with that name was already
174164
defined, the existing sentinel with that name will be returned. Sentinels
175-
with the same name in different modules will be distinct from each other.
165+
with the same name defined in different modules will be distinct from each
166+
other.
176167

177168
Creating a copy of a sentinel object, such as by using ``copy.copy()`` or by
178169
pickling and unpickling, will return the same object.
179170

180-
The ``module_name`` optional argument should normally not need to be supplied,
181-
as ``Sentinel()`` will usually be able to recognize the module in which it was
182-
called. ``module_name`` should be supplied only in unusual cases when this
183-
automatic recognition does not work as intended, such as perhaps when using
184-
Jython or IronPython. This parallels the designs of ``Enum`` and
185-
``namedtuple``. For more details, see :pep:`435`.
171+
``Sentinel()`` will also accept a single optional argument, ``module_name``.
172+
This should normally not need to be supplied, as ``Sentinel()`` will usually
173+
be able to recognize the module in which it was called. ``module_name``
174+
should be supplied only in unusual cases when this automatic recognition does
175+
not work as intended, such as perhaps when using Jython or IronPython. This
176+
parallels the designs of ``Enum`` and ``namedtuple``. For more details, see
177+
:pep:`435`.
186178

187-
The ``Sentinel`` class may not be sub-classed, to avoid overly-clever uses
188-
based on it, such as attempts to use it as a base for implementing singletons.
189-
It is considered important that the addition of Sentinel to the stdlib should
190-
add minimal complexity.
179+
The ``Sentinel`` class may not be sub-classed, to avoid the greater complexity
180+
of supporting subclassing.
191181

192182
Ordering comparisons are undefined for sentinel objects.
193183

@@ -240,17 +230,7 @@ methods, returning :py:class:`typing.Union` objects.
240230
Backwards Compatibility
241231
=======================
242232

243-
While not breaking existing code, adding a new "sentinels" stdlib module could
244-
cause some confusion with regard to existing modules named "sentinels", and
245-
specifically with the "sentinels" package on PyPI.
246-
247-
The existing "sentinels" package on PyPI [10]_ appears to be abandoned, with
248-
the latest release being made on Aug. 2016. Therefore, using this name for a
249-
new stdlib module seems reasonable.
250-
251-
If and when this PEP is accepted, it may be worth verifying if this has indeed
252-
been abandoned, and if so asking to transfer ownership to the CPython
253-
maintainers to reduce the potential for confusion with the new stdlib module.
233+
This proposal should have no backwards compatibility implications.
254234

255235

256236
How to Teach This
@@ -277,15 +257,12 @@ simplified version follows::
277257
class Sentinel:
278258
"""Unique sentinel values."""
279259

280-
def __new__(cls, name, repr=None, bool_value=True, module_name=None):
260+
def __new__(cls, name, module_name=None):
281261
name = str(name)
282-
repr = str(repr) if repr else f'<{name.split(".")[-1]}>'
283-
bool_value = bool(bool_value)
262+
284263
if module_name is None:
285-
try:
286-
module_name = \
287-
sys._getframe(1).f_globals.get('__name__', '__main__')
288-
except (AttributeError, ValueError):
264+
module_name = sys._getframemodulename(1)
265+
if module_name is None:
289266
module_name = __name__
290267

291268
registry_key = f'{module_name}-{name}'
@@ -296,24 +273,18 @@ simplified version follows::
296273

297274
sentinel = super().__new__(cls)
298275
sentinel._name = name
299-
sentinel._repr = repr
300-
sentinel._bool_value = bool_value
301276
sentinel._module_name = module_name
302277

303278
return _registry.setdefault(registry_key, sentinel)
304279

305280
def __repr__(self):
306-
return self._repr
307-
308-
def __bool__(self):
309-
return self._bool_value
281+
return self._name
310282

311283
def __reduce__(self):
312284
return (
313285
self.__class__,
314286
(
315287
self._name,
316-
self._repr,
317288
self._module_name,
318289
),
319290
)
@@ -377,7 +348,7 @@ A sentinel class decorator
377348

378349
The suggested idiom is::
379350

380-
@sentinel(repr='<NotGiven>')
351+
@sentinel
381352
class NotGivenType: pass
382353
NotGiven = NotGivenType()
383354

@@ -421,21 +392,40 @@ idiom were unpopular, with the highest-voted option being voted for by only
421392
25% of the voters.
422393

423394

395+
Allowing customization of repr
396+
------------------------------
397+
398+
This was desirable to allow using this for existing sentinel values without
399+
changing their repr. However, this was eventually dropped as it wasn't
400+
considered worth the added complexity.
401+
402+
403+
Using ``typing.Literal`` in type annotations
404+
--------------------------------------------
405+
406+
This was suggested by several people in discussions and is what this PEP
407+
first went with. However, it was pointed out that this would cause potential
408+
confusion, due to e.g. ``Literal["MISSING"]`` referring to the string value
409+
``"MISSING"`` rather than being a forward-reference to a sentinel value
410+
``MISSING``. Using the bare name was also suggested often in discussions.
411+
This follows the precedent and well-known pattern set by ``None``, and has the
412+
advantages of not requiring an import and being much shorter.
413+
414+
424415
Additional Notes
425416
================
426417

427418
* This PEP and the initial implementation are drafted in a dedicated GitHub
428419
repo [7]_.
429420

430421
* For sentinels defined in a class scope, to avoid potential name clashes,
431-
one should use the fully-qualified name of the variable in the module. Only
432-
the part of the name after the last period will be used for the default
433-
repr. For example::
422+
one should use the fully-qualified name of the variable in the module. The
423+
full name will be used as the repr. For example::
434424

435425
>>> class MyClass:
436426
... NotGiven = sentinel('MyClass.NotGiven')
437427
>>> MyClass.NotGiven
438-
<NotGiven>
428+
MyClass.NotGiven
439429

440430
* One should be careful when creating sentinels in a function or method, since
441431
sentinels with the same name created by code in the same module will be
@@ -482,7 +472,6 @@ Footnotes
482472
.. [7] `Reference implementation at the taleinat/python-stdlib-sentinels GitHub repo <https://github.com/taleinat/python-stdlib-sentinels>`_
483473
.. [8] `bpo-35712: Make NotImplemented unusable in boolean context <https://github.com/python/cpython/issues/79893>`_
484474
.. [9] `Discussion thread about type signatures for these sentinels on the typing-sig mailing list <https://mail.python.org/archives/list/[email protected]/thread/NDEJ7UCDPINP634GXWDARVMTGDVSNBKV/#LVCPTY26JQJW7NKGKGAZXHQKWVW7GOGL>`_
485-
.. [10] `sentinels package on PyPI <https://pypi.org/project/sentinels/>`_
486475
487476
488477
Copyright

0 commit comments

Comments
 (0)