Modern Python Types
Typing Module Introduction
Python's dynamic typing is flexible but can lead to runtime errors and unclear APIs. The typing module enables static type checking, better IDE support, and self-documenting code without runtime performance impact, catching bugs before they reach production.
Why Use Type Hints?
Generic Types
Use TypeVar and Generic to create type-safe generic functions and classes.
basic_hints.py
# Basic type hints for functions and variables
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def is_adult(age: int) -> bool:
return age >= 18
# Variable type hints
username: str =
age: int =
height: float = 5.8
is_active: bool = True
# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")
message = greet("Bob")
print(message)
print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def is_adult(age: int) -> bool:
return age >= 18
# Variable type hints
username: str =
age: int =
height: float = 5.8
is_active: bool = True
# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")
message = greet("Bob")
print(message)
print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def is_adult(age: int) -> bool:
return age >= 18
# Variable type hints
username: str =
age: int =
height: float = 5.8
is_active: bool = True
# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")
message = greet("Bob")
print(message)
print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def is_adult(age: int) -> bool:
return age >= 18
# Variable type hints
username: str =
age: int =
height: float = 5.8
is_active: bool = True
# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")
message = greet("Bob")
print(message)
print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def is_adult(age: int) -> bool:
return age >= 18
# Variable type hints
username: str =
age: int =
height: float = 5.8
is_active: bool = True
# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")
message = greet("Bob")
print(message)
print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def is_adult(age: int) -> bool:
return age >= 18
# Variable type hints
username: str =
age: int =
height: float = 5.8
is_active: bool = True
# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")
message = greet("Bob")
print(message)
print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def is_adult(age: int) -> bool:
return age >= 18
# Variable type hints
username: str =
age: int =
height: float = 5.8
is_active: bool = True
# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")
message = greet("Bob")
print(message)
print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def is_adult(age: int) -> bool:
return age >= 18
# Variable type hints
username: str =
age: int =
height: float = 5.8
is_active: bool = True
# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")
message = greet("Bob")
print(message)
print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
# Basic type hints for functions and variables
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def is_adult(age: int) -> bool:
return age >= 18
# Variable type hints
username: str =
age: int =
height: float = 5.8
is_active: bool = True
# Using the functions
result = add(10, 20)
print(f"10 + 20 = {result}")
message = greet("Bob")
print(message)
print(f"Is 25 adult? {is_adult(25)}")
print(f"Is 15 adult? {is_adult(15)}")
collections.py
# Collection type hints
from typing import List, Dict, Set, Tuple
def process_numbers(numbers: List[int]) -> int:
return sum(numbers)
def get_scores() -> Dict[str, int]:
return {"Alice": 95, "Bob": 87, "Charlie": 92}
def unique_values(values: List[int]) -> Set[int]:
return set(values)
def get_coordinates() -> Tuple[float, float, float]:
return (10.5, 20.3, 30.7)
# Usage
nums = [1, 2, 3, 4, 5]
total = process_numbers(nums)
print(f"Sum: {total}")
scores = get_scores()
print(f"Scores: {scores}")
unique = unique_values([1, 2, 2, 3, 3, 3])
print(f"Unique: {unique}")
x, y, z = get_coordinates()
print(f"Coordinates: ({x}, {y}, {z})")
optional.py
# Optional type for nullable values
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
"""Returns username or None if not found"""
users = {1: "Alice", 2: "Bob", 3: "Charlie"}
return users.get(user_id)
def divide(a: float, b: float) -> Optional[float]:
"""Returns result or None if division by zero"""
if b == 0:
return None
return a / b
# Usage
user = find_user(2)
if user is not None:
print(f"Found user: {user}")
else:
print("User not found")
user = find_user(999)
if user is not None:
print(f"Found user: {user}")
else:
print("User not found")
# Division
result = divide(10, 2)
print(f"10 / 2 = {result}")
result = divide(10, 0)
print(f"10 / 0 = {result}")
union.py
# Union types for multiple allowed types
from typing import Union
def format_value(value: Union[int, float, str]) -> str:
"""Format different types of values"""
if isinstance(value, (int, float)):
return f"Number: {value}"
return f"Text: {value}"
def process_id(id_value: Union[int, str]) -> str:
"""Process ID that can be int or string"""
return f"ID-{id_value}"
# Usage
print(format_value(42))
print(format_value(3.14))
print(format_value("hello"))
print(process_id(1001))
print(process_id("ABC123"))
# Function that returns different types
def get_config(key: str) -> Union[str, int, bool]:
config = {
"host": "localhost",
"port": 8080,
"debug": True
}
return config.get(key, "")
print(f"Host: {get_config('host')}")
print(f"Port: {get_config('port')}")
print(f"Debug: {get_config('debug')}")
callable.py
# Callable type for function parameters
from typing import Callable, List
def apply_operation(numbers: List[int], operation: Callable[[int], int]) -> List[int]:
"""Apply operation to each number"""
return [operation(n) for n in numbers]
def filter_values(numbers: List[int], predicate: Callable[[int], bool]) -> List[int]:
"""Filter numbers using predicate function"""
return [n for n in numbers if predicate(n)]
# Define operations
def double(x: int) -> int:
return x * 2
def square(x: int) -> int:
return x * x
def is_even(x: int) -> bool:
return x % 2 == 0
# Usage
numbers = [1, 2, 3, 4, 5]
doubled = apply_operation(numbers, double)
print(f"Doubled: {doubled}")
squared = apply_operation(numbers, square)
print(f"Squared: {squared}")
evens = filter_values(numbers, is_even)
print(f"Even numbers: {evens}")
# Using lambda
odds = filter_values(numbers, lambda x: x % 2 != 0)
print(f"Odd numbers: {odds}")
generics.py
# Generic types with TypeVar
from typing import TypeVar, List, Generic
T = TypeVar('T')
def get_first(items: List[T]) -> T:
"""Get first item from list, preserving type"""
return items[0]
def get_last(items: List[T]) -> T:
"""Get last item from list, preserving type"""
return items[-1]
# Generic class
class Stack(Generic[T]):
def __init__(self):
self._items: List[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
def is_empty(self) -> bool:
return len(self._items) == 0
# Usage with different types
numbers = [1, 2, 3, 4, 5]
print(f"First number: {get_first(numbers)}")
print(f"Last number: {get_last(numbers)}")
words = ["hello", "world", "python"]
print(f"First word: {get_first(words)}")
print(f"Last word: {get_last(words)}")
# Generic stack
int_stack: Stack[int] = Stack()
int_stack.push(10)
int_stack.push(20)
int_stack.push(30)
print(f"Popped: {int_stack.pop()}")
str_stack: Stack[str] = Stack()
str_stack.push("a")
str_stack.push("b")
print(f"Popped: {str_stack.pop()}")
@seealso dataclass_intro "Dataclasses with type hints" @seealso namedtuple_intro "Typed NamedTuples"
type hint An annotation that specifies the expected type of a variable, function parameter, or return value, enabling static analysis and better documentation.
Optional A type hint indicating a value can be either the specified type or None, equivalent to Union[T, None].
Union A type hint that accepts any of the specified types, useful when a value can legitimately be different types.
Callable A type hint for function parameters or variables that hold callable objects, specifying the expected parameter types and return type.
Generic A base class for creating generic types that work with any type while maintaining type safety, using TypeVar for type parameters.