With the recent addition of sentinels to Python 3.15, I would like to write code like this:
from typing import NamedTuple
TRANSPARENT = sentinel("TRANSPARENT")
class Rgb(NamedTuple):
r: int
g: int
b: int
type Color = TRANSPARENT | Rgb
def f(color: Color) -> None:
match color:
case TRANSPARENT: # always matches!
print("it's transparent!")
case Rgb(r, g, b): # never reached!
print(f"({r}, {g}, {b})")
but this doesn’t work as-is, because the case TRANSPARENT arm always matches because it’s interpreted as a capture variable instead of as a constant.
The usual work-around is to put the constant into a namespace:
class MyNamespace:
TRANSPARENT = sentinel("TRANSPARENT")
type Color = MyNamespace.TRANSPARENT | Rgb
def f(color: Color) -> None:
match color:
case MyNamespace.TRANSPARENT:
print("it's transparent!")
case Rgb(r, g, b):
print(f"({r}, {g}, {b})")
This works, but it feels cumbersome.
I think one natural way to express that you want to match on a constant that’s not in a namespace, is to put a . in front of it:
match color:
case .TRANSPARENT: # <- dot in front of the name, not a capture variable
print("it's transparent!")
case Rgb(r, g, b):
print(f"({r}, {g}, {b})")
The syntax .CONST seems to me to intuitively convey the meaning of “look up this constant in the current scope”, similar to how from .module import ... looks for module in the current directory.
As additional motivation, consider how the same code would look like if I were using string literals instead of sentinels:
from typing import Literal
type Color = Literal["transparent"] | Rgb
def f(color: Color) -> None:
match color:
case "transparent":
print("it's transparent!")
case Rgb(r, g, b):
print(f"({r}, {g}, {b})")
I don’t need to care about namespaces here at all.
And it would be great if sentinels were to become a fully-fledged alternative to string literals in Python.