Narrowing¶
This example shows each form the checker narrows on, one function each.
Flow typing is what lets a strict checker accept ordinary Python: after
if name is None: return 0, name is a str; after
isinstance(value, str) fails, value is an int.
Run it¶
Guards, boolean operators, the walrus¶
@ppy.pure
def boolean_guard(name: str | None) -> bool:
return name is not None and len(name) > 0
@ppy.pure
def walrus_guard(values: list[int]) -> int:
if (count := len(values)) == 0:
return 0
return count
andnarrows its right operand:len(name)is checked withname: str.ornarrows the code after it. Inearly_default, the body afterif name is None or len(name) == 0: return -1sees a non-emptystr.- A walrus binding is a local the checker tracks like any other.
by_classnarrows withisinstance.
match¶
@ppy.pure
def by_match(value: int | str | None) -> str:
match value:
case None:
return "none"
case int():
return f"int:{value + 1}"
case _:
return f"str:{value.upper()}"
A case sees the subject with the earlier cases subtracted. The int() case
sees int | str, and the wildcard sees only str, so value.upper()
checks without a cast.
A case with a guard rules nothing out for the cases after it, because a guard can fail for reasons the pattern does not express.
All six functions are pure and native.
What it prints¶
python narrowing.ppy, ppy narrowing.ppy, ppy run narrowing.ppy
Read on: Classes ยท The subset
narrowing.ppy is hand-written; there is no .py source and no conversion
step.
10_narrowing/narrowing.ppy¶
import ppy
@ppy.pure
def optional_guard(name: str | None) -> int:
if name is None:
return 0
return len(name)
@ppy.pure
def boolean_guard(name: str | None) -> bool:
return name is not None and len(name) > 0
@ppy.pure
def early_default(name: str | None) -> int:
if name is None or len(name) == 0:
return -1
return len(name)
@ppy.pure
def by_class(value: int | str) -> int:
if isinstance(value, str):
return len(value)
return value
@ppy.pure
def by_match(value: int | str | None) -> str:
match value:
case None:
return "none"
case int():
return f"int:{value + 1}"
case _:
return f"str:{value.upper()}"
@ppy.pure
def walrus_guard(values: list[int]) -> int:
if (count := len(values)) == 0:
return 0
return count
def main() -> None:
print(optional_guard("ppy"), optional_guard(None))
print(boolean_guard("ppy"), boolean_guard(""), boolean_guard(None))
print(early_default("ppy"), early_default(""), early_default(None))
print(by_class("four"), by_class(7))
print(by_match(None), by_match(41), by_match("hi"))
print(walrus_guard([1, 2, 3]), walrus_guard([]))
main()
Source: examples/10_narrowing.