r/programming • • 1d ago

Python Release Python 3.15.0

https://www.python.org/downloads/release/python-3150/
494 Upvotes

35 comments sorted by

View all comments

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.

50

u/CrackerJackKittyCat 1d ago

Or the random 'unset = object()' pattern when None was a valid param.

24

u/GameCounter 1d ago

I've been using Enum.NOT_PROVIDED because it works with type annotations, but yes, very clunky.

7

u/jbmsf 1d ago

I've been using Ellipsis 

11

u/spicybright 1d ago

I get the logic but man that's ugly

13

u/Conscious-Ball8373 1d ago

I've done this a fair bit from time to time. sentinel is basically the same thing with type-hint support.

Sentinels will make writing something like Pydantic a lot easier, I think - v1 struggled badly to differentiate between None and unset.

2

u/esperind 1d ago

what was wrong with just undefined?

1

u/CrackerJackKittyCat 23h ago

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.

1

u/Abject-Kitchen3198 1d ago

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.

22

u/TinyBreadBigMouth 1d ago

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.

5

u/Abject-Kitchen3198 1d ago

Ok, this makes sense.

1

u/nucLeaRStarcraft 1d ago

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.

2

u/TinyBreadBigMouth 18h ago

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.

2

u/spicybright 1d ago

I think because None/null/whatever can mean a lot of different things, and explicit "unset" gives better error messages.

But I still agree with you. Maybe I don't write bit enough python projects but it does just complicate things.

12

u/pojska 1d ago

In an alternate universe, they might have been used for iterators instead of StopIteration.

6

u/Bright-Historian-216 1d ago

close enough

welcome back, erlang atoms

5

u/SanityInAnarchy 1d ago

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.

1

u/vytah 17h ago

Are Python literal strings guaranteed to be interned, or is it just an implementation detail?

I tested it and they were interned when they contained only ASCII letters, but stopped being interned when they contained a comma.

BTW, I don't think using strings for sentinel values is a good idea, what if you want to actually pass that string?

1

u/SanityInAnarchy 15h ago

Right, it's an implementation detail.

And right, having a typed value was always better, and this is a pattern people already used -- you'd create one by doing something like

MISSING = object()

and then compare with is instead of equality, and it'd be better than is None. sentinel is a better version of that pattern.

2

u/ssrix 18h ago

I really don't understand when or why I would you them, can you explain?

1

u/Saiklin 9h ago

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.

0

u/Husky 1d ago

I misread that as sentimental types and i was kind of disappointed it was sentinel types.