Syntax Definition¶
Valid stenotype
annotations will always cleanly map to regular type
annotations. The conversion rules are collected in this document.
How to Annotate Functions and Variables¶
Function signature annotation was added with PEP 3017 and properly defined in PEP 484. This annotation is made up of two parts: The argument annotation and the return value annotation:
def foo(a: A, b: B) -> R:
...
Variable annotation was added a little later with PEP 526, and written as follows:
foo: Var_Type
Since a function is just another object, it can also be annotation in this way. For example, the function from above could also have been annotated as follows:
from typing import Callable
foo: Callable[[A, B], R]
This structure will remain as is. stenotype
only influences the way types
are expressed, not where they can be meaningfully used.
All but the variable notation of function types will also stay structurally similar. Given the examples, they would change in the following ways:
# function signature notation
def foo(a: "A", b: "B") -> "R":
...
# variable notation of plain variables
foo: "Var_Type"
# variable notation of functions
foo: "(A, B) -> R"
typing
-Types Equivalent Mappings¶
Special Forms¶
Special forms use reserved symbols to represent special types.
Any: Is used in cases where you have to specify a type but do not want to place restrictions on it.
regular
foo: typing.Any
stenotype
foo: "_"
foo: "object"
Union:
If a number of types are admissible for a single variable, just list them all
with an or
between them.
regular
foo: typing.Union[int, float]
stenotype
foo: "int or float"
Optional:
A special case of Union
, where the only other option besides the specified
type for a variable is None
. stenotype
prepends a question mark, which
is what a few other languages allow as well.
regular
foo: typing.Optional[int]
stenotype
foo: "?int"
Containers¶
All containers are specified by their literal notation.
Tuple:
Fixed size ()
literal with element types specified for each position.
Tuples may also be defined with flexible length using ...
to arbitrarily repeat the
preceding type.
value
foo = (1, 'two', 3.0)
bar = (1, 2, 3, 4, 5, 6)
regular
foo: typing.Tuple[int, str, float]
bar: typing.Tuple[int, ...]
stenotype
foo: "(int, str, float)"
bar: "(int, ...)"
List:
Variable size []
literal with all elements of the same type.
For mixed element types, use a union.
value
foo = [1, 2, 3, 4, 5, 6]
bar = [1, 'two', 3, 4, 'five', 6]
regular
foo: typing.List[int]
foo: typing.List[Union[int, str]]
stenotype
foo: "[int]"
foo: "[int or str]"
Dict:
Variable size {}
literal with all keys and value of the same type, respectively.
For mixed element types, use a union.
value
foo = {'one': 1, 'three': 3, 'two': 2}
regular
foo: typing.Dict[str, int]
stenotype
foo: "{str: int}"
Set: Set notation is identical to list notation, the difference between them is not relevant for the annotation.
value
foo = {1, 2, 3, 5, 6, 4}
regular
foo: typing.Set[int]
stenotype
foo: "{int}"
Signatures¶
Specifying the signature of functions is important for wrappers (e.g. decorators), callbacks and higher-order functions. It also allows annotating functions from third-party libraries which lack annotations.
Callable: In situations where it’s not possible to annotate a function in its signature, its name can be accessed at a later point in time to add type info. This can also be used to specify the type of callbacks or higher-order functions.
value
def foo(a: str, b: int) -> int:
...
regular
foo: typing.Callable[[str, int], int]
stenotype
foo: "(str, int) -> int"
If the parameters of the signature are unknown or not relevant,
use ...
for the arguments – e.g. (...) -> int
.
Note
Variadic and Named Arguments:
When the number of arguments is arbitrary, the *
and **
symbols are used.
Similar to list and dict, the type of all variadic arguments is the same; use a
union if multiple are accepted.
If names of arguments are part of the signature, these can be annotated similar to
dictionary keys. Use name: Type
instead of just Type
. Keyword-only
arguments are implied by following a *
argument, positional-only arguments
by preceding an empty /
argument.
value
def foo(a: str, /, b: int, *c: bool, d: bytes, **e: float) -> int:
...
regular
class Signature(Protocol):
def __call__(a: str, /, b: int, *c: bool, d: bytes, **c: float) -> int:
...
foo: Signature
stenotype
foo: "(str, /, b: int, *c: bool, d: bytes, **float) -> int"
Names of positional and variadic arguments can be omitted; keyword-only arguments must always have a name.
Common Types¶
Iterable:
The Iterable interface is primarily used for containers that you only plan to
use in loops and for return types of generators. Use the * in either context
when using stenotype
.
regular
foo: typing.Iterable[int]
bar: typing.Callable[[], typing.Iterable[bool]]
stenotype
foo: "iter int"
bar: "() -> iter bool"
Literal: Literal values are not really types, but they still can be meaningfully used in annotations. Most often, they will be part of a union, since they’d otherwise just be constants.
Types can’t be expressed as literals in stenotype
mode, since they’d be
interpreted as instances of that type, and not the actual type object.
Only constants (True
, False
, None
, Ellipsis
) and primitive literals
(str
, int
, float
) are valid for literal types.
regular
foo: typing.Union['foo', 'bar', 'baz', 1]
stenotype
foo: "'foo' or 'bar' or 'baz' or 1"
Meta Types¶
regular
# code
stenotype
# code
regular
# code
stenotype
# code
regular
# code
stenotype
# code
Special Function Qualifiers¶
You can use a limited set of keywords to describe a number of common types. These are especially useful when annotating the return types of functions. Keywords correspond to how the type is used to get a value of a specifi type.
Asynchronous function
value
async def foo(a: A, b: B) -> R:
return r
bar = foo(a, b)
regular
foo: typing.Callable[[A, B], typing.Awaitable[R]]
bar: typing.Awaitable[R]
stenotype
foo: "(A, B) -> await R"
bar: "await R"
Asynchronous generator
value
async def foo(a: A, b: B) -> AsyncIterable[R]:
yield r
bar = foo(a, b)
regular
foo: typing.Callable[[A, B], typing.AsyncIterable[R]]
bar: typing.AsyncIterable[R]
stenotype
foo: "(A, B) -> await iter R"
bar: "await iter R"
Context managing functions
value
@contextmanager
def foo(a: A, b: B):
yield r
regular
foo: typing.Callable[[A, B], typing.ContextManager[R]
stenotype
foo: "(A, B) -> with R"