Given these definitions:
from typing import NamedTuple, TypeAlias
class A(NamedTuple):
x: int
y: str
class B(NamedTuple):
x: int
y: str
it would be nice if I could write the following:
AB: TypeAlias = A | B
match ab:
case AB(x, y):
print(f"{x=}, {y=}")
instead of having to write:
match ab:
case A(x, y) | B(x, y):
print(f"{x=}, {y=}")
That is, it would be great if the union type was expanded into an or-pattern in match-case.
Motivation: the above example is a bit silly. Where this would in practice be helpful is if you have a union over subclasses from a single base class:
from abc import abstractmethod
from dataclasses import dataclass
@dataclass(frozen=True)
class Base:
x: bool
y: int | str
@abstractmethod
def f(self) -> str:
raise NotImplementedError()
@dataclass(frozen=True)
class A(Base):
y: int # narrowed type
def f(self) -> str:
return "I'm class A"
@dataclass(frozen=True)
class B(Base):
def f(self) -> str:
return "I'm class B"
# This gives us a type which can be used for exhaustiveness checking.
# We can't use `Base` for that because it might have other subclasses.
AB: TypeAlias = A | B
When we want to distinguish A
and B
, we can exhaustively check over AB
:
def f(ab: AB):
match ab:
case A(x, y):
print(f"an int: {y}")
case B(x, y):
print(f"either str or int: {y}")
But we can also match on the whole thing at once:
def f(ab: AB):
match x:
case AB(x, y):
print(f"either str or int: {y}")
Specification: There would be a runtime error if the __match_args__
of the union elements aren’t all the same.
Other justification: unions already work with isinstance
:
isinstance(ab, AB)
so why not case
as well?