python type hint,  python typing

Python type hints

Primer to statically indicating types in python

Python type hints

Photo by Javier Allegue Barros on Unsplash

This is my hopefully simple to follow Python type hints primer. I will only cover a few of the common types hints that should get a beginner started.

Type hints are Pythons formal solution to statically indicate the types of variables. This is however not used by the python runtime in most cases but can be useful for type checkers, IDEs, linters etc.

Built in types

Any type, built in or user defined can be used as a type hint. Below are some of the common built in types.

integer: int = 10

float_value: float = 10.5

boolean: bool = True

string: str = 'hello there'

byte_array: bytearray = bytearray([1, 2])

bytes_value: bytes = bytes([1, 2])

complex_number: complex = complex(10, 5)

mapping: dict = {'x': 10}

# an immutable unordered collection of unique elements
frozen_set: frozenset = frozenset([1, 2])

# mutable sequence
list_collection: list = [1, 2]
memory_view: memoryview = memoryview(bytearray([1, 2]))

# the most base type
object_value: object = object()

# n unordered collection of unique elements
set_collection: set = {1, 2, 3}

# immutable sequence
tuple_collection: tuple = 2, 5

type_value: type = type(integer)

# a zip object whose .__next__() method returns a tuple where
# the i-th element comes from the i-th iterable argument
zip_object: zip = zip([1, 2], ['A', 'B'])

# the enumerate object yields pairs containing a count (from start, which
# defaults to zero) and a value yielded by the iterable argument
enumerate_object: enumerate = enumerate([1, 2])

Any

This is the ‘catch all’ type hint you should avoid using unless in the process of porting code to support type hints. This simply means the variable/argument can be of any type.

  • Any is compatible with every type.
  • Any assumed to have all methods.
  • All values assumed to be instances of Any.

object is superficially similar to Any at a glance but different. Object is the root of Python’s metaclass hierarchy but is still restrictive in that the only methods you are permitted to call are ones that are a part of the object definition, unlike Any. Any is an escape hatch meant to allow you to mix together dynamic and statically typed code.

from typing import Any

# variable can be assigned to any type
unconstrained: Any
unconstrained = 10


# argument passed can be of any type
# and method can return any type
def sample(unconstrained: Any) -> Any:
    pass

NoReturn

Special type indicating that a function never returns as in case of function that loops infinitely or always raises an exception.

This type is invalid in other positions, e.g., List[NoReturn]

from typing import NoReturn


# this will never return
def raise_always() -> NoReturn:
    raise RuntimeError()

Callable

Reference to a function or lambda that can be called.


from typing import Callable


def to_int(value: str) -> int:
    return int(value)


# constrained callable accepting one string arg and return integer
convert_to_int: Callable[[str], int] = to_int
# unconstrained callable accepting any number of args of any type and returning any
double: Callable = lambda x: x * 2
# return type constrained callable accepting any number of args of any type and return nothing
print_out: Callable[..., None] = lambda x: print(x)

print_out(double(convert_to_int('250')))
# 500

print(callable(convert_to_int))
print(callable(double))
print(callable(print_out))
# True
# True
# True

ClassVar

Marks class variables (static).


from typing import ClassVar


class Sample:
    name: str
    nationality: ClassVar[str] = 'World Citizen'  # class variable

    def __init__(self, name):
        self.name = name


sample = Sample('Shingayi')
Sample.nationality = 'World Citizen'

print(sample.nationality)
# World Citizen

TypeVar

Allows for creation of generic variable, useful for generic methods.

T = TypeVar(‘T’) # Can be anything A = TypeVar(‘A’, str, bytes) # Must be str or bytes

from typing import TypeVar, Sequence, Callable, Mapping

# unconstrained generic
T = TypeVar('T')


def pass_through(input: T) -> T:
    return input


# constrained generic
U = TypeVar('U', int, float)


def double(input: U) -> U:
    return input * 2


print(double(5))
print(double(5.0))
# 10
# 10.0

# multiple generics
K = TypeVar('K')
V = TypeVar('V')


def group_by(items: Sequence[V], key_func: Callable[[V], K]) -> Mapping[K, V]:
    pass

Literal

A type that can be used to indicate to type checkers that the corresponding variable or function parameter has a value equivalent to the provided literal (or one of several literals).


from typing import Literal

MODE = Literal['r', 'rb', 'w', 'wb']


def open_file(path: str, mode: MODE) -> None:
    pass


open_file('', 'r')
open_file('', 'x')  # warning expected Literal['r', 'rb', 'w', 'wb']

Generic

The abstract base class for creating generic types.


from typing import Generic, TypeVar

K = TypeVar('K')
V = TypeVar('V')


class Mapping(Generic[K, V]):
    pass

Optional

Optional type that can be a specific type or None.

Optional[X] is equivalent to Union[X, None]


from typing import Optional, Union

can_be_none: Optional[str] = None
cannot_be_none: str = 'hi there'

sample: Optional[int]
# is same as
sample2: Union[None, int]

Tuple

Tuple definition type.

Deprecated since version 3.9: builtins.tuple now supports []


from typing import Tuple

# Tuple of two ints
sample: Tuple[int, int] = 1, 5
# same as, but more expressive
sample2: Tuple = 1, 5
# same as, not expressive also
sample3: tuple = 1, 5

# Variable length tuple with items of homogenous type (int in this case)
sample: Tuple[int, ...] = 1, 5, 6, 10, 20