Dynamic boundaries¶
ppy.dynamic is the escape hatch for code strict PPy rejects, and this
example shows what it costs. Strict PPy rejects getattr with a computed
name, attributes it cannot resolve, and values it cannot type. ppy.dynamic
is where you declare a piece of code Python in the old sense, and the
checker holds the line at its edge.
Run it¶
What it prints¶
python dynamic.ppy, ppy dynamic.ppy, ppy run dynamic.ppy
Three spellings¶
def reflective(name: str) -> str:
with ppy.dynamic():
return str(getattr("abc", name)())
@ppy.dynamic
def duck_typed(obj: object) -> str:
return f"{obj.__class__.__name__}"
def normalize(payload: ppy.Dynamic) -> int:
return int(payload)
- Context manager.
ppy.dynamic()permits the computedgetattrfor a block. - Decorator.
@ppy.dynamicmakes a whole function dynamic:obj.__class__on anobjectis fine inside. - Annotation.
ppy.Dynamicmarks a value that arrives untyped. It isAnyat run time, but spelled as a decision rather than an inference failure.
int(payload) is the conversion that turns it into something typed;
ppy.check[int] is the checked form. Nothing dynamic leaks out untyped.
What a boundary costs¶
A boundary is an optimization barrier. Native code stops at it, and
static_only (the plain function in the file) is the one that lowers.
A project that wants no boundaries sets [tool.ppy] dynamic-boundaries =
"deny" and gets E1505 at the first one.
Migration goes the other way: ppy migrate converts a dynamic file
faithfully and marks each site, and ppy check then asks for the boundary.
Where the code comes from¶
dynamic.ppy is hand-written; there is no .py source and no conversion step.
Read on: The subset · Effects and contracts · Migration
16_dynamic/dynamic.ppy¶
import ppy
@ppy.pure
def static_only(values: list[int]) -> int:
total: int = 0
for value in values:
total += value
return total
def reflective(name: str) -> str:
with ppy.dynamic():
return str(getattr("abc", name)())
@ppy.dynamic
def duck_typed(obj: object) -> str:
return f"{obj.__class__.__name__}"
def normalize(payload: ppy.Dynamic) -> int:
return int(payload)
def main() -> None:
print(static_only([1, 2, 3, 4]))
print(reflective("upper"))
print(duck_typed(3), duck_typed("s"), duck_typed([]))
print(normalize("41") + 1)
main()
Source: examples/16_dynamic.