Join us and get access to thousands of tutorials and a community of expert Pythonistas.
This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.
Talking About Sentinels
00:00 In the previous lesson, I showed you the new frozen dictionary and the ability to unpack values in a comprehension. In this lesson, I’ll talk about sentinels.
00:10 Before getting to the new feature, let me tell you a little story. Consider a web application where you have some data on a form, which when you change it, live updates to the equivalent data on the server.
00:21 There are many ways of doing this, but one of the most common is to use the REST protocol. If you changed the first name in the form, some JavaScript code fires, invoking an HTTP PUT method.
00:35 Inside of it, you would have some JSON telling the server about the new state. Notice how all the values on the left-hand side are being sent to the server.
00:45 That can be expensive, especially if you have a lot of data. Instead of using HTTP PUT, you can use HTTP PATCH, which only expects you to send along the thing that changed. Let me edit the first name again.
01:02 And this time, a much smaller JSON blob gets generated. Where this gets interesting depends on what tools you’re using. You might use something like Pydantic to validate the JSON and make sure what was sent makes sense.
01:16
Pydantic gives a whole object to your code, marking the fields that didn’t change as None. You can then loop through the attributes of the Pydantic object, ignoring anything that is None, and calling setattr() on those that have a value, affecting the change.
01:31 This has a complication, though.
01:34 Let’s change the use case a bit. What happens when I delete a value on the left-hand side altogether?
01:42
I could use an empty string here, but that only works because it’s a string. For a more complicated data type, like an object, I might need to set the value to null, which is JavaScript’s equivalent of None.
01:54
Let’s pretend that city is something a little more complicated. Have you figured out the problem? I can send null up to the server in the JSON, but how does Pydantic distinguish between values that didn’t change, marked with None, and a value that got changed to null, which, in Python, is None?
02:13 This is a problem. I’ve actually had to write specialty code that reads what was sent and processes it after Pydantic has done validation because of exactly this situation.
02:24
Fundamentally, None is a valid value in some cases, which creates the problem of distinguishing between None, the valid value, and None, the signal that something is empty.
02:35
A placeholder representing empty or end of stream is known as a sentinel. Sometimes None is a sentinel, and sometimes None is a value.
02:44
You may have seen various sentinels in Python already. The find() method on a string object returns the position of the thing looked for. If the thing isn’t found, it returns -1.
02:55 You can’t use zero because that’s a valid position, the first spot in the string. Negative one, in that case, is a sentinel. Other practices include using a bare instance of an object, a custom class, or an enum, which really is just a special case of a custom class.
03:13
Python 3.15 introduces a new explicit sentinel object, which I’ll show you now. You create a sentinel using the new built-in sentinel() function. When you create a sentinel, you give it a name.
03:28 When you inspect a sentinel, you see the name it was created with.
03:33
As you might expect, the object has a type sentinel. You can also control how the sentinel gets displayed when it’s printed out.
03:43
The optional repr argument allows you to determine how it is displayed. For the most part, sentinels are objects and can do object things.
03:58 Like work in a membership test. They’re also hashable, so they can be used as a dictionary key. There is one thing you might not expect, though.
04:10
The name you pass in is not a unique identifier. Each time you call sentinel(), you get a new object.
04:18
Meaning, amongst other things, an is test fails for two calls to the function. This isn’t a big deal. It just means that if you’re going to use a sentinel in a bunch of places in your code, you should define it once at the top of a module as a global variable and use that global variable throughout.
04:36 You can also specify a sentinel as a valid choice in the type system.
04:48 Now, when I inspect the function definition,
04:57
I see MISSING is one of the valid types. I mentioned that a common sentinel before was an instance of the generic object class.
05:05 That mucked with type systems because everything is an object, meaning you couldn’t restrict the type. By using the new sentinel, you can strictly type your code and indicate missing values. In the next lesson, I’ll show you some things that make your life a little easier when you code.
Become a Member to join the conversation.