decorating anything¶
python allows a decorator on a def and a class. basedpython allows one above
a binding too, and in a type position:
transpiles to:
on a binding¶
a decorator above a binding means what it means on a definition: it is applied to what is bound, so the name holds whatever the decorator returned
transpiles to:
it reads on every binding that has a value — a plain assignment, an annotated
one, and the let / var / class declarations:
| basedpython | python output |
|---|---|
@foo + x = 1 |
x = foo(1) |
@foo + x: int = 1 |
x: int = foo(1) |
@foo + let a = 1 |
a: Final = foo(1) |
@foo + var x: int = 1 |
x: int = foo(1) |
@foo + let a: T = 1 |
a: Final[T] = foo(1) |
a declaration with no value binds nothing for a decorator to wrap, and carrying one there is an error:
so is a decorator above anything that is not a definition or a binding:
the binding keeps its own type¶
Annotated[T, …] is T, so the decorator does not change what the name holds —
it records something alongside it
the decorator is an ordinary expression, evaluated where it is written, so a name
that does not resolve there is an unresolved-reference
more than one¶
a chain reads innermost first — the decorator written closest to the binding is
the one nearest the type, the same order @a @b int puts them in
transpiles to:
on a type¶
a decorator written in a type position attaches metadata to the type it
precedes. it is what Annotated spells
transpiles to:
the type is the one decorated — the decorator says nothing about it, so b
above is an int and every Annotated[int, meta] is assignable to it and it to
them. the decorator is an ordinary expression, evaluated where it is written, so
a name that does not resolve there is an unresolved-reference
it reads in every type position, including a nested one:
| basedpython | python output |
|---|---|
@meta int |
Annotated[int, meta] |
@field(gt=0) int |
Annotated[int, field(gt=0)] |
list[@meta int] |
list[Annotated[int, meta]] |
def f() -> @meta int |
def f() -> Annotated[int, meta] |
precedence¶
a decoration runs to the end of the type it is written on, the way a decorator on
a def covers the whole definition rather than its first line. a
use-site type modifier is the other way round — it takes the
operand it precedes and nothing more
transpiles to:
the postfix ? is read at the same level as |, so it is inside the decoration
too: @meta int? is the decorated optional
decorating only part of a union means saying where the decoration ends, which is what a group does
transpiles to:
the decorator itself is read as a primary expression — a name, an attribute path, a call, or a subscript — because nothing separates it from the type it decorates. a parenthesized group after it is therefore ambiguous, and which one it is depends on what follows: it is the decorator's call arguments when a type comes after it, and the decorated type when nothing does
| basedpython | reads as |
|---|---|
@meta (int \| None) |
meta decorating int \| None |
@field (gt=0) int |
field(gt=0) decorating int |
@meta() int |
meta() decorating int |
a chain¶
each decorator adds metadata and none of them changes the type. a chain
collapses into one Annotated, whose metadata reads in the order the decorators
apply
transpiles to: