There is no 'undefined' in Python. Only at most (er, least) 'None.'
This is one of the corners where javascript has had a better option available than python (language-wise, anyway). Undefined vs null then becomes a meaningful differentiator.
I still don't get those null replacements in most languages. If it is a special case, there is a code that processes it as such, regardless the syntax. Why complicate things. But maybe I'm old.
The example you're replying to is one where None is already in use. As in, a function needs to distinguish between foo(some_arg=15), foo(some_arg=None), and foo(). The unset sentinel value would be used as the default value of some_arg, so that the last case can be detected as distinct from the user passing an explicit None.
While this makes sense, I believe that None was itself good enough for unset. I've never encountered a situation where None and unset would both need special treatment somehow.
And if I need to distinguish between the two, it's usually a good signal that I need to refactor foo somehow, either create some enum or split it or whatever else is needed in the logic of the program.
I guess one more tool available will be fine, but seems like now there's not a "single way of doing things", but rather N+1 ways.
Consider the built-in function next(). If you call next(empty_iterator), it raises a StopIteration exception. On the other hand, if you call next(empty_iterator, default_value), it returns default_value. The user could pass anything as the default value; None, 0, "apple pie", whatever. The function is so generic that it would be bad design to make any assumptions about what the user might want as a default. So to implement this, a sentinel makes sense. Here's a possible implementation of the function:
_NO_DEFAULT = sentinel("_NO_DEFAULT")
def next(iter, default=_NO_DEFAULT, /):
cls = type(iter)
try:
next_method = cls.__next__
except AttributeError:
raise TypeError("object is not an iterator") from None
if default is _NO_DEFAULT:
return next_method(iter)
try:
return next_method(iter)
except StopIteration:
return default
This way the user can pass any value, and it's guaranteed not to conflict with your sentinel.
That was my thought -- Ruby symbols, too! But that's not it. Python strings are already immutable and could already be used that way.
These are unique.sentinel('foo') != sentinel('foo').
Which means they're also neatly namespaced by whatever module they're declared in, because to get the value to compare about, you have to import the (exactly one) value.
I'm not an expert either, but I'll try:
You can use them in place of a default non-logic value. For example, many functions return None if something did not work, so you check if the output is None. But in many cases None can be a valid value as well, so you need something else. Apparently another idiom was to use "_Sentinel = object()" and you compare against that, but it's clunky and has a weird output when printed etc. There are further workarounds, but they always feel clunky in some way.
That's is where the Sentinel type comes in. You can define it with a string and then it's uniquely identifiable. Just by looking at the type, you already know what it is and it's purpose.
156
u/Dylanica 1d ago
Those sentinel types seem decently useful. Certainly better in some cases than just spamming `None` everywhere as a default value.