Python type hints

What is Python type hint?
Optional “type hints” (also called “type annotations”), special syntax that allow declaring the type of objects. By declaring types for your variables, editors and tools can give you better support and analyze your code.

Do type hints affect the runtime behavior of your code?
Type hints normally don’t affect the runtime behavior of your code. Think of type hints as similar to comments!

Python has rich typing support, we can check related Typing PEPs.

In this note I collect some of the most important features, recipes related to type hints. Initial information is from FastAPI python types intro.

I also have commented code with type hints examples and explanations: python/type_hints.py.

Type hints features

  • Editor provide additional support for type hints (autocomplete, analyze, etc.).
  • Type check for static analysis.
  • For libraries, type hints can be used to generate documentation, define requirements, convert data, validate data, document the API using OpenAPI.

Basic examples

Initial code snippets, without any types:

def get_full_name(first_name, last_name):
    full_name = first_name.title() + " " + last_name.title()
    return full_name
 
 
print(get_full_name("john", "doe"))  # John Doe

Rewritten with type hints:

def get_full_name(first_name: str, last_name: str) -> str:
    full_name = first_name.title() + " " + last_name.title()
    return full_name
 
 
print(get_full_name("john", "doe"))  # John Doe

Type hints help us to analyze code better, in following example we get type hint warning, ’■ Operator ”+” not supported for types “str” and “int”‘:

def get_name_with_age(name: str, age: int) -> str:
 
    name_with_age = name + " is this old: " + age  # need to use str(age) here
    return name_with_age

Python types and type hints

We can use following basic types:

  • int - integer
  • float - floating point number
  • str - string
  • bool - boolean
def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes):
    return item_a, item_b, item_c, item_d, item_d, item_e
 
print(get_items("a", 1, 1.0, True, b"b"))

There are some data structures that can contain other values, like dict, list, set and tuple. And the internal values can have their own type too (list(int)). These types that have internal types are called "generic" types. And it’s possible to declare them, with optional internal types.

Python standard module typing provide runtime support for these type hints, but newer Python version can make this obsolete.

As Python advances, newer versions come with improved support for these type annotations and in many cases you won’t even need to import and use the typing module to declare the type annotations.

List and tuples type hints

We can declare variable type as list of integers with following syntax:
variable_name: list[str], in the brackets are type parameters. Usage in the function:

def process_items(items: list[str]):  # each list sequence item is a string
    for item in items:
        print(item)  # you can play with autocomplete here
 
def process_items(items_t: tuple[int, int, str], items_s: set[bytes]):
    """
    Process items with type hints.
 
    Args:
        items_t: tuple with 3 items, an int, another int, and a str.
        items_s: set of bytes.
    """
    return items_t, items_s

Dict type hints

To define a dict, you pass 2 type parameters, separated by commas (for key and value types).

def process_items(prices: dict[str, float]):  # key/value type parameter
    for item_name, item_price in prices.items():
        print(item_name)
        print(item_price)

Union type hints

You can declare that a variable can be any of several types, for example, a int or a str, str or None. In newer Python version we can use special union syntax, types separated by ==a vertical bar (|)==.

def process_item(item: int | str):  # item could be an int or a str
    print(item)
 
def say_hi(name: str | None = None):  # item could be possible None
  if name is not None:
      print(f"Hey {name}!")
  else:
      print("Hello World")

Classes as types

You can also declare a class as the type of variable.

class Person:
    def __init__(self, name: str):
        self.name = name
 
 
def get_person_name(one_person: Person):
    return one_person.name  # you can play with autocomplete here

Metadata Annotations

We can put additional metadata in type hints using Annotated (Python 3.9+). The first type parameter you pass to Annotated is the actual type. The rest, is just metadata for other tools.

Python itself doesn’t do anything with this Annotated. And for editors and other tools, the type is still ==str==. But you can use this space in Annotated to provide additional information for application for custom behavior.

from typing import Annotated
 
 
def say_hello(name: Annotated[str, "this is just metadata"]) -> str:
    return f"Hello {name}"

Custom libraries