1

I have a value that always needs to be defined but may not always be meaningful, and I need a placeholder for when it doesn't yet have a meaningful value. Usually, I would use None as that placeholder value. For example, this code is supposed to ask for a Python literal, asking again every time the user inputs something that is not a Python literal:

import ast

user_input = None
while user_input is None:
    try:
        user_input = ast.literal_eval(input("Enter a Python literal: "))
    except:
        pass

This is one use case, but I've also used None as a placeholder in data structures, loops, and other cases.

However, None is sometimes itself a meaningful value! In the above example, the user should be allowed to input None, but they can't since that won't terminate the while loop. In other cases, they might want to store None in a tree or something similar, so I wouldn't be able to use None to represent missing values.

Since this pattern is useful in cases besides breaking out of a loop, I'm looking for a general alternative to a None in situations where a None does not work. What should I use as a placeholder value when None isn't allowed?

12
  • What do you expect them to type to stop the loop? Commented 21 hours ago
  • If they can enter any value, you need to separate the prompt for the value from the prompt for whether to continue the loop. Commented 21 hours ago
  • "This is one use case, but I've also used this pattern to create placeholders in data structures, to control loops, and in other instances." --- this covers too many different use cases. A properly focused question would focus on a single use case only. For instance, data structures: we would need to know the data structure and how you want to use this sentinel value. For loops? We need the loop and how you expect to use the sentinel value. I think these would require different solutions, hence my vote to close as needing focus. Commented 21 hours ago
  • It looks as though all you're trying to achieve is to parse the user input such that any attempt to so will not raise an exception. In which case while True: Commented 20 hours ago
  • 2
    @Barmar The point is not the example. The point is that it concisely demonstrates a use case for a sentinel. Commented 18 hours ago

2 Answers 2

7

You should probably use a sentinel value. In Python 3.14 and earlier, this is typically (though not always!) done using a bare object combined with an identity (is) check:

import ast

SENTINEL = object()
user_input = SENTINEL
while user_input is SENTINEL:
    try:
        user_input = ast.literal_eval(input("Enter a Python literal: "))
    except:
        pass

The benefit of this pattern is that SENTINEL is completely unique. No other user-created object will ever compare identical to it. As such, there's no chance of collision with user-provided input; even another object() will not be identical to your sentinel. In fact, you can create multiple unique sentinels for different use cases:

SENTINEL_1 = object()
SENTINEL_2 = object()
assert SENTINEL_1 is not SENTINEL_2

Be careful to always use identity, not equality (==), when comparing to sentinels. Otherwise, someone could redefine __eq__ on the other object in an undesirable way:

SENTINEL = object()
class AlwaysEqual:
    def __eq__(self, other, /):
        return True
assert SENTINEL is not AlwaysEqual()
assert SENTINEL == AlwaysEqual() # Bad!

In Python 3.15, using an object like this will no longer be necessary. PEP 661 introduced a built-in sentinel class, which is very similar to the old pattern:

SENTINEL = sentinel("SENTINEL")
assert SENTINEL is not None
SENTINEL_2 = sentinel("SENTINEL_2")
assert SENTINEL is not SENTINEL_2

Like an object sentinel, new sentinels should always be compared with is and not ==.

It will also play nicely with type hints (though in this case, a bare None would also work):

SENTINEL = sentinel("SENTINEL")
five_characters: str | SENTINEL = SENTINEL

while five_characters is SENTINEL:
    user_input = input("Enter five characters: ")
    if len(user_input == 5):
        five_characters = user_input

Before 3.14, this wouldn't have worked; you generally would've needed to introduce more overhead such as using a class rather than an object as your sentinel value.

Sign up to request clarification or add additional context in comments.

5 Comments

How can a user type in a Python expression that is that specific object SENTINEL?
They wouldn't unless I'm misunderstanding the original question. If the input fails and an error is caught, the user_input will remain as the sentinel.
@Booboo the question is unclear; are you asking for something different to five_characters is SENTINEL, which is in the answer?
I was looking at the first code example posted by the OP. It makes no sense to me since the user can never enter something that when evaluated is SENTINEL.
You don't want them to @Booboo, the point is to have a value that represents no input independent of any value the user could enter (in this case not None because they could enter 'None').
-1

I'm probably missing the point here but I just can't see why you would need any kind of sentinel in this use-case. Just wrap it in a function as follows:

import ast
from typing import Any

def get_user_input() -> Any:
    while True:
        try:
            inval = input("Enter a Python literal: ")
            return ast.literal_eval(inval)
        except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError):
            pass

1 Comment

The point is that the use case isn't that important and exists solely to demonstrate a None sentinel failing. The question is not literally asking "how do I fix my input code"; it's asking "what do I do when None doesn't work".

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.