Beyond simple types (int, str), Python's typing system covers optional values, unions of multiple types, and callables — all without affecting runtime behavior.
from typing import Optional, Union, Callable
def find_user(id: int) -> Optional[str]: # Optional[str] means "str or None"
return "Rahim" if id == 1 else None
def process(value: Union[int, str]) -> str: # accepts either an int or a str
return str(value)
# Python 3.10+ shorthand for both of the above — preferred in new code:
def find_user_new(id: int) -> str | None: # | replaces Union[..., None]/Optional
return "Rahim" if id == 1 else None
def process_new(value: int | str) -> str:
return str(value)
def apply(func: Callable[[int, int], int], a: int, b: int) -> int: # func's own signature
return func(a, b)
apply(lambda x, y: x + y, 3, 4) # 7
Optional[X] is shorthand for Union[X, None] — "a value of type X, or None" — and is one of the most common hints in real code, since so many functions can legitimately return "nothing found." Union[X, Y] means "either X or Y."
Python 3.10 introduced the X | Y syntax as a cleaner, built-in alternative to Union[X, Y] (and X | None in place of Optional[X]) — no import needed, and it's now the preferred style in modern code, with the older typing.Union/typing.Optional forms mostly seen in code targeting older Python versions.
Callable[[ArgType1, ArgType2], ReturnType] describes a function's own signature as a type — useful for parameters, like apply above, that expect to receive another function and want to document what that function's signature should look like.