
I've created a poll on this subject: https://discuss.python.org/t/sentinel-values-in-the-stdlib/8810 On Fri, May 14, 2021 at 10:33 PM Tal Einat <taleinat@gmail.com> wrote:
On Fri, May 14, 2021 at 12:45 PM Steve Dower <steve.dower@python.org> wrote:
On 14May2021 0622, micro codery wrote:
There was a discussion a while back ( a year or so?? ) on Python-ideas that introduced the idea of having more "sentinel-like" singletons in Python -- right now, we only have None.
Not quite true, we also have Ellipsis, which already has a nice repr that both reads easily and still follows the convention of eval(repr(x)) == x. It also is already safe from instantiation, survives pickle round-trip and is multi-thread safe. So long as you are not dealing with scientific projects, it seems a quick (if dirty) solution to having a sentinel that is not None.
I don't think using "..." to indicate "some currently unknown or unspecified value" is dirty at all, it seems perfectly consistent with how we use it in English (and indexing in scientific projects, for that matter, where it tends to imply "figure out the rest for me").
All that's really missing is some kind of endorsement (from python-dev, presumably in the docs) that it's okay to use it as a default parameter value. I can't think of any reason you'd need to accept Ellipsis as a *specified* value that wouldn't also apply to any other kind of shared sentinel.
I'll try to organize my thoughts a bit here. This is a bit long, welcome to skip to the final sentence for the "tl;dr".
Features one may want for a sentinel: 1. Unique from other objects 2. Globally unique, i.e. unique from other such sentinels (no consensus here) 3. Clear repr (the original subject of this thread) - significant since these often appear in function signatures 4. Survives pickling round-trip (I brought this up since I was bitten by this once, but others have mentioned that this is usually irrelevant) 5. Can be given a clear type signature (this was brought up on twitter[1]) - significant since without this nobody can add full type signatures even if they want to
The common `SENTINEL = object()` idiom fails #3, #4 and #5. This is what I've been using for years, and I now think that it isn't good enough. This not having a nice repr is what started this thread.
I'd also personally prefer something simple, ideally without a new class or module.
There are several simple idioms using existing features that seem like good options, so let's review those:
1. Ellipsis, a.k.a. `...`. This has all of the features outlined above except #2. My main issue with using Ellipsis for this is that it could be surprising and confusing for devs first encountering such use, and could be relatively awkward to figure out.
2. An instance of a one-off class. dataclasses.MISSING is an example of this. It is defined thus:
class _MISSING: pass MISSING = _MISSING()
Besides failing #4 (surviving pickle round-trips), its repr isn't great: <dataclasses._MISSING object at 0x7fe14b1e2e80>. That is easily overcome by implementing __repr__.
3. A one-off class:
class MISSING: pass
This has all of the above features except #5: having a clear type signature (since its type is type). Using a class as a value this way could be surprising, though. It's repr also isn't great: <class 'dataclasses._MISSING'>.
4. A value of an single-valued enum, for example (from [1]):
class _UNSET(enum.Enum): token = enum.auto()
This has all of the features above and is simple, just requiring a comment to explain what it is. It's repr is a bit awkward though:
repr(_UNSET.token) '<_UNSET.token: 1>'
All of these are in use by some developers, though not necessarily in the stdlib. None is perfect, though all are probably good enough. Since pickling is likely not relevant in most cases, I'm currently in favor of #2 making sure to implement a nice __repr__.
- Tal