Annotations

Documentation

  1. Python Glossary:
    1. annotation and type hint
    2. variable annotation and function annotation
  2. Python tutorial:
    1. Function annotations
  3. Python Language Reference:
    1. Annotated assignment statements
    2. parameter and return value annotations

Annotate a variable

import sys
import datetime

i = 10
j: "number of jelly beans" = 20 #The variable is an int, but the annotation is a str.
k: int = 30                     #The variable is an int, and the annotation is a data type.
l: int                          #Does not create a variable.
k = "A"                         #Deliberately violate the type annotation.

s = "hello"
t: str = "goodbye"

b: bool = True
f: float = 123.456
li: list = [10, 20, 30]
tu: tuple = (10, 20, 30)
se: set = {10, 20, 30}
d: dict = {"ten": 10, "twenty": 20, "thirty": 30}
r: range = range(10)
now: datetime.datetime = datetime.datetime.now()

dictionaryOfGlobals = globals()                          #dictionaryOfGlobals is a dict
try:
    annotations = dictionaryOfGlobals["__annotations__"] #dictionaryOfAnnotations is another dict.
except KeyError as error:
    print(error, file = sys.stderr)
    sys.exit(1)

print(f'The annotation for the variable j is "{annotations["j"]}".')
print()

for key, value in annotations.items():
    print(key, value)

sys.exit(0)
The annotation for the variable j is "number of jelly beans".

j number of jelly beans
k <class 'int'>
l <class 'int'>
t <class 'str'>
b <class 'bool'>
f <class 'float'>
li <class 'list'>
tu <class 'tuple'>
se <class 'set'>
d <class 'dict'>
r <class 'range'>
now <class 'datetime.datetime'>

Run mypy

To avoid the following errors, remove the silly "number of jelly beans" annotation in line 5 and the k = "A" in line 8.

mypy prog.py
prog.py:5: error: Invalid type comment or annotation
prog.py:8: error: Incompatible types in assignment (expression has type "str", variable has type "int")
Found 2 errors in 1 file (checked 1 source file)

Annotate a function

There are three possibilities. The function might be called correctly (r == 0), the function might be called incorrectly (r == 1), or the function might not be called at all (r == 2).

import sys
import random

def printer(x, i = 2):
    """
    Print x with i digits to the right of the decimal point.
    x must be an int or float.
    i must be an int, and defaults to 2.
    """
    print(f"x = {x:.{i}f}")

r = random.randrange(3)   #0, 1, or 2

if r == 0:
    printer(123.456, 2)   #correct
elif r == 1:
    printer(2, 123.456)   #incorrect (This is line 17.)

print("Everything is okay.")
sys.exit(0)
x = 123.46
Everything is okay.
Traceback (most recent call last):
  File "/Users/mark/python/junk.py", line 17, in 
    printer(2, 123.456)   #incorrect (line 17)
  File "/Users/mark/python/junk.py", line 10, in printer
    print(f"x = {x:.{i}f}")
ValueError: Invalid format specifier


Annotate the function signature like this:

def printer(x: float, i: int = 2) -> None:

Annotate the variable r like this:

r: int = random.randrange(3)   #0, 1, or 2
mypy prog.py
prog.py:17: error: Argument 2 to "printer" has incompatible type "float"; expected "int"

list of floats vs.
list of strings

import sys
import typing   #not to be confused with Taiping

def printList(li: list) -> None:
    "This function accepts any list."
    listOfStrings = [str(item) for item in li]
    print(f'printList: {" ".join(listOfStrings)}')


def printListOfFloats(li: typing.List[float]) -> None:
    "This function demands a list of floats."
    listOfStrings = [f"{f:6.2f}" for f in li]
    print(f'printListOfFloats: {" ".join(listOfStrings)}')


def printContainerOfFloats(it: typing.Iterable[float]) -> None:
    """
    This function accepts a list of floats, or a tuple of floats,
    or a set of floats, or any iterable container of floats.
    """
    listOfStrings = [f"{f:6.2f}" for f in it]
    print(f'printContainerOfFloats: {" ".join(listOfStrings)}')


listOfStrs:      typing.List[str] = ["Moe", "Larry", "Curly"]
listOfFloats:    typing.List[float] = [10.0, 20.0, 30.0]
tupleOfFloats:   typing.Tuple[float, float, float] = (10.0, 20.0, 30.0)
tupleOfFloatEtc: typing.Tuple[float, ...] = (10.0, 20.0, 30.0)
setOfFloats:     typing.Set[float] = {10.0, 20.0, 30.0}

printList(listOfStrs)
printListOfFloats(listOfFloats)
print()

printContainerOfFloats(listOfFloats)
printContainerOfFloats(tupleOfFloats)
printContainerOfFloats(tupleOfFloatEtc)
printContainerOfFloats(setOfFloats)

#printListOfFloats(listOfStrs)
sys.exit(0)
printList: Moe Larry Curly
printListOfFloats:  10.00  20.00  30.00

printContainerOfFloats:  10.00  20.00  30.00
printContainerOfFloats:  10.00  20.00  30.00
printContainerOfFloats:  10.00  20.00  30.00
printContainerOfFloats:  10.00  20.00  30.00
mypy prog.py
Success: no issues found in 1 source file

If you uncomment the bad statement before the exit,

mypy prog.py
prog.py:40: error: Argument 1 to "printListOfFloats" has incompatible type "List[str]"; expected "List[float]"
Found 1 error in 1 file (checked 1 source file)

A list of lists

import sys
import typing

listOfLists: typing.List[typing.List[int]] = [
    [10, 20, 30],
    [11, 21, 31]
]

for row in listOfLists:
    for i, n in enumerate(row):
        end = "\n" if i == len(row) - 1 else " "
        print(n, end = end)

sys.exit(0)
10 20 30
11 21 31
mypy prog.py
Success: no issues found in 1 source file

If you change the 10 to 10.0,

mypy prog.py
prog.py:5: error: List item 0 has incompatible type "float"; expected "int"
Found 1 error in 1 file (checked 1 source file)