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

TypeVar

regular

# code

stenotype

# code

Generic

regular

# code

stenotype

# code

ForwardRef

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"