Type Checking/Annotations in Python

  • Type Annotations are a new feature added in PEP 484 that allow for adding type hints to variables.
  • They are used to inform someone reading the code what the type of a variable should be.
  • This brings a sense of statically typed control to the dynamically typed Python
  • Usage
    • Annotate Argument
    • Annotate Return type
    • Annotate Variable
  • Benefits
    • Makes the code easier to read for you and your team,
    • Encourages you to have types in mind,
    • Helps to identify type-related issues,
    • Enables proper type checking.
  • Python interpreter actually does not automatically conduct any type checking whatsoever.
  • That means those annotations have no effects during runtime, even a ‘wrong’ type is passed
  • With annotation, the IDE is aware of the type of the data object
  • And gives warning if type is miss-matched

Basic

Without Annotation

def midrange(data):
    return (max(data) + min(data)) / 2

print(mr([1, 4, 8]))
print(mr({1: 1, 2: 4, 3: 8}))

With Annotation

# Expected: return-type --> float
def midrange(data) -> float:
    # IDE warning: expected float for str
    return 'Amrit'
print(midrange({1: 1, 2: 4, 3: 8}))


# Expected: input-type --> list
def midrange(data: list):
    return (max(data) + min(data)) / 2
# IDE warning: expected list, got dict
print(midrange({1: 1, 2: 4, 3: 8}))

Advance

  • typing module
from typing import List, TypeVar, Union, cast
def fib_list(n: int = 0) -> List[int]:
    fib_numbers: List[int] = [0, 1]
    for _ in range(n):
        fib_numbers.append(fib_numbers[-1] + fib_numbers[-2])

    # IDE w:  if not list
    fib_numbers = "Amrit"
    return fib_numbers

print(f"fib_list(10) = {fib_list(10)}")

# typing.TypeVar: Define Generics in Python
T = TypeVar('T')  # Can be anything
A = TypeVar('A', str, bytes)  # Must be str or bytes
# Union: accept multiple types
# Optional[x]:  is simply short hand for Union[x, None]
def upcase(s: Union[str, bytes]) -> Union[str, bytes]:
    if isinstance(s, str):
        return s.upper()
    elif isinstance(s, bytes):
        print('byte')
        return bytes(x - 0x20 if 0x61 <= x <= 0x7A else x for x in s)
    else:
        raise TypeError("need str or bytes")

print(upcase(b"amrit"))
# Type-Casting
def type_cast():
    # accept any type
    value = [130]

    # accept only int
    value_float = cast(List[int], value)

    value.append("string")
    value_float.append("string")

Reference