如何在不使用None的情况下设置占位符(哨兵值)?
我有一个值需要始终被定义,但它并不总是有意义,我需要一个占位符来表示它还没有有意义的值。通常,我会把 None 作为该占位符值。比如,这段代码本应要求输入一个Python字面量,每次用户输入的不是Python字面量时就再次询问:
import ast
user_input = None
while user_input is None:
try:
user_input = ast.literal_eval(input("Enter a Python literal: "))
except:
pass
这是一个用例,但我也把 None 用作数据结构、循环等场景中的占位符。
然而,None 有时本身也是一个有意义的值!在上面的例子中,应该允许用户输入 None,但他们不能,因为那样不会终止while循环。在其他情况下,他们可能想把 None 存储在树或类似结构中,因此我就不能使用 None 来表示缺失值。
由于这种模式在跳出循环之外的场景也很有用,我正在寻找一个在 None 不起作用时,针对 None 的通用替代方案。当 None 不被允许时,我应该使用什么作为占位符值?
解决方案
你可能应该使用一个 哨兵值。在Python 3.14及更早版本中,这通常(尽管并非总是如此)是通过一个裸的 object 与一个同一性检查(is)的组合来实现:
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
这种模式的好处在于 SENTINEL 完全唯一。没有任何其他用户创建的对象会与之完全相同。因而,与用户提供的输入不会发生冲突;即使另一个 object() 也不会与你的哨兵完全相同。实际上,你可以为不同的用例创建多个独一无二的哨兵:
SENTINEL_1 = object()
SENTINEL_2 = object()
assert SENTINEL_1 is not SENTINEL_2
在与哨兵比较时,请务必始终使用同一性比较(identity),而不是相等比较(equality)(==),否则,某些人可能会在另一个对象上以不良方式重新定义 __eq__:
SENTINEL = object()
class AlwaysEqual:
def __eq__(self, other, /):
return True
assert SENTINEL is not AlwaysEqual()
assert SENTINEL == AlwaysEqual() # Bad!
在Python 3.15中,使用像这样的 object 将不再是必要的。PEP 661 引入了一个 内置的哨兵类,与旧模式非常相似:
SENTINEL = sentinel("SENTINEL")
assert SENTINEL is not None
SENTINEL_2 = sentinel("SENTINEL_2")
assert SENTINEL is not SENTINEL_2
像一个 object 哨兵一样,新哨兵应始终与 is 进行比较,而不是与 ==。
它在类型提示方面也能很好地配合使用(尽管在这种情况下,裸的 None 也可以工作):
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
在3.14之前,这种做法将不起作用;通常你需要引入更多开销,比如使用一个类而不是对象作为你的哨兵值。