python

examples

examples.py🐍
"""
Type Hints and Static Typing in Python - Examples

Type hints add optional static typing to Python.
They improve readability, enable better tooling, and catch bugs early.
"""

from typing import (
    Optional, Union, Any, 
    Callable, TypeVar, Generic,
    Literal, TypedDict, Protocol,
    overload, Final, ClassVar
)
from collections.abc import Iterable, Iterator, Sequence
from dataclasses import dataclass
import abc

# =============================================================================
# BASIC TYPE HINTS
# =============================================================================

# Variables
name: str = "Alice"
age: int = 30
height: float = 5.8
is_active: bool = True

# None type
nothing: None = None

# Multiple types using Union (Python 3.10+ use |)
user_id: int | str = "user123"
maybe_value: int | None = None  # Same as Optional[int]

# Functions with type hints
def greet(name: str) -> str:
    """Function with type hints."""
    return f"Hello, {name}!"


def add(a: int, b: int) -> int:
    """Simple typed function."""
    return a + b


def process_data(data: str, count: int = 1) -> list[str]:
    """Function with default parameter."""
    return [data] * count


# Functions returning None
def log_message(message: str) -> None:
    """Functions that don't return a value."""
    print(message)


# =============================================================================
# COLLECTION TYPES
# =============================================================================

# Lists
numbers: list[int] = [1, 2, 3, 4, 5]
names: list[str] = ["Alice", "Bob", "Charlie"]

# Tuples - fixed length and types
point: tuple[int, int] = (10, 20)
record: tuple[str, int, float] = ("Alice", 30, 5.8)
variable_tuple: tuple[int, ...] = (1, 2, 3, 4, 5)  # Variable length

# Dictionaries
scores: dict[str, int] = {"Alice": 100, "Bob": 85}
config: dict[str, str | int | bool] = {
    "debug": True,
    "port": 8080,
    "host": "localhost"
}

# Sets
unique_ids: set[int] = {1, 2, 3}
tags: frozenset[str] = frozenset(["python", "typing"])

# Nested collections
matrix: list[list[int]] = [[1, 2], [3, 4], [5, 6]]
users_data: dict[str, dict[str, Any]] = {
    "alice": {"age": 30, "email": "alice@example.com"}
}


# =============================================================================
# OPTIONAL AND NONE HANDLING
# =============================================================================

def find_user(user_id: int) -> str | None:
    """Return user name or None if not found."""
    users = {1: "Alice", 2: "Bob"}
    return users.get(user_id)


def process_user(user_id: int) -> str:
    """Example of handling Optional return values."""
    user = find_user(user_id)
    if user is None:
        return "Unknown"
    # After the check, type checker knows user is str
    return user.upper()


def greet_optional(name: str | None = None) -> str:
    """Optional parameter with None default."""
    if name is None:
        return "Hello, stranger!"
    return f"Hello, {name}!"


# =============================================================================
# CALLABLE TYPES
# =============================================================================

# Function types
Processor = Callable[[str], str]
Validator = Callable[[int], bool]
NoArgsFunc = Callable[[], None]
AnyFunc = Callable[..., Any]  # Any arguments


def apply_processor(text: str, processor: Processor) -> str:
    """Function taking another function as parameter."""
    return processor(text)


def create_multiplier(factor: int) -> Callable[[int], int]:
    """Function returning a function."""
    def multiplier(n: int) -> int:
        return n * factor
    return multiplier


# Example usage
double = create_multiplier(2)
result = apply_processor("hello", str.upper)


# =============================================================================
# TYPE VARIABLES (GENERICS)
# =============================================================================

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


def first_element(items: list[T]) -> T | None:
    """Generic function that works with any list type."""
    return items[0] if items else None


def reverse_dict(d: dict[K, V]) -> dict[V, K]:
    """Generic function with multiple type variables."""
    return {v: k for k, v in d.items()}


# Bounded type variables
Number = TypeVar('Number', int, float)


def add_numbers(a: Number, b: Number) -> Number:
    """Type variable bounded to numeric types."""
    return a + b


# =============================================================================
# GENERIC CLASSES
# =============================================================================

class Stack(Generic[T]):
    """A generic stack implementation."""
    
    def __init__(self) -> None:
        self._items: list[T] = []
    
    def push(self, item: T) -> None:
        self._items.append(item)
    
    def pop(self) -> T:
        return self._items.pop()
    
    def peek(self) -> T | None:
        return self._items[-1] if self._items else None
    
    def is_empty(self) -> bool:
        return len(self._items) == 0


# Usage
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)

str_stack: Stack[str] = Stack()
str_stack.push("hello")


class Pair(Generic[K, V]):
    """Generic class with two type parameters."""
    
    def __init__(self, key: K, value: V) -> None:
        self.key = key
        self.value = value
    
    def swap(self) -> 'Pair[V, K]':
        return Pair(self.value, self.key)


# =============================================================================
# LITERAL TYPES
# =============================================================================

Direction = Literal["north", "south", "east", "west"]
HttpMethod = Literal["GET", "POST", "PUT", "DELETE"]


def move(direction: Direction) -> str:
    """Only accepts specific literal values."""
    return f"Moving {direction}"


def make_request(method: HttpMethod, url: str) -> dict:
    """Only accepts valid HTTP methods."""
    return {"method": method, "url": url}


# Usage
move("north")  # OK
# move("up")   # Type error!


# =============================================================================
# TYPED DICTIONARIES
# =============================================================================

class UserDict(TypedDict):
    """Typed dictionary with specific keys and types."""
    name: str
    age: int
    email: str


class PartialUserDict(TypedDict, total=False):
    """All fields are optional."""
    name: str
    age: int
    email: str


class MixedUserDict(TypedDict):
    """Mix of required and optional fields."""
    name: str  # Required
    age: int   # Required
    email: str | None  # Can be None


def process_user_data(user: UserDict) -> str:
    """Function accepting typed dictionary."""
    return f"{user['name']} ({user['age']})"


# Usage
user: UserDict = {"name": "Alice", "age": 30, "email": "alice@example.com"}


# =============================================================================
# PROTOCOLS (STRUCTURAL SUBTYPING)
# =============================================================================

class Drawable(Protocol):
    """Protocol defining drawable interface."""
    
    def draw(self) -> str:
        ...


class Resizable(Protocol):
    """Protocol for resizable objects."""
    
    def resize(self, factor: float) -> None:
        ...


class Circle:
    """Class that implements Drawable without explicit inheritance."""
    
    def __init__(self, radius: float):
        self.radius = radius
    
    def draw(self) -> str:
        return f"Circle with radius {self.radius}"


class Square:
    """Another drawable class."""
    
    def __init__(self, side: float):
        self.side = side
    
    def draw(self) -> str:
        return f"Square with side {self.side}"
    
    def resize(self, factor: float) -> None:
        self.side *= factor


def render(shape: Drawable) -> None:
    """Works with any class that has draw() method."""
    print(shape.draw())


# Works because Circle has draw() method
render(Circle(5))
render(Square(10))


# =============================================================================
# OVERLOADED FUNCTIONS
# =============================================================================

@overload
def process(value: int) -> int: ...

@overload
def process(value: str) -> str: ...

@overload
def process(value: list[int]) -> list[int]: ...

def process(value: int | str | list[int]) -> int | str | list[int]:
    """
    Overloaded function with different signatures.
    Type checker knows the return type based on input type.
    """
    if isinstance(value, int):
        return value * 2
    elif isinstance(value, str):
        return value.upper()
    else:
        return [x * 2 for x in value]


# Type checker knows these types:
int_result: int = process(5)        # Returns int
str_result: str = process("hello")  # Returns str
list_result: list[int] = process([1, 2, 3])  # Returns list[int]


# =============================================================================
# FINAL AND CLASS VARIABLES
# =============================================================================

# Constants that shouldn't be reassigned
MAX_SIZE: Final[int] = 100
API_URL: Final = "https://api.example.com"


class Config:
    """Class with class variables and instance variables."""
    
    # Class variable (shared by all instances)
    version: ClassVar[str] = "1.0.0"
    
    # Instance variable
    name: str
    
    def __init__(self, name: str) -> None:
        self.name = name


# =============================================================================
# TYPE ALIASES
# =============================================================================

# Simple type aliases
UserId = int
Email = str
JsonDict = dict[str, Any]

# Complex type aliases
Handler = Callable[[str, int], dict[str, Any]]
Matrix = list[list[float]]
Tree = dict[str, 'Tree | int']  # Recursive type alias


def send_email(to: Email, subject: str) -> bool:
    """Using type alias for clarity."""
    return True


def get_user(user_id: UserId) -> dict[str, Any]:
    """Type alias makes code more readable."""
    return {"id": user_id, "name": "User"}


# =============================================================================
# DATACLASSES WITH TYPE HINTS
# =============================================================================

@dataclass
class Point:
    """Dataclass with full type hints."""
    x: float
    y: float
    label: str = ""
    
    def distance_from_origin(self) -> float:
        return (self.x ** 2 + self.y ** 2) ** 0.5


@dataclass
class Rectangle:
    """Another typed dataclass."""
    width: float
    height: float
    
    def area(self) -> float:
        return self.width * self.height


# =============================================================================
# ABSTRACT BASE CLASSES WITH TYPE HINTS
# =============================================================================

class Repository(abc.ABC, Generic[T]):
    """Abstract generic repository."""
    
    @abc.abstractmethod
    def get(self, id: int) -> T | None:
        """Get item by ID."""
        pass
    
    @abc.abstractmethod
    def save(self, item: T) -> int:
        """Save item and return ID."""
        pass
    
    @abc.abstractmethod
    def delete(self, id: int) -> bool:
        """Delete item by ID."""
        pass


@dataclass
class User:
    id: int
    name: str
    email: str


class UserRepository(Repository[User]):
    """Concrete implementation of generic repository."""
    
    def __init__(self) -> None:
        self._users: dict[int, User] = {}
        self._next_id = 1
    
    def get(self, id: int) -> User | None:
        return self._users.get(id)
    
    def save(self, item: User) -> int:
        if item.id == 0:
            item.id = self._next_id
            self._next_id += 1
        self._users[item.id] = item
        return item.id
    
    def delete(self, id: int) -> bool:
        if id in self._users:
            del self._users[id]
            return True
        return False


# =============================================================================
# TYPE GUARDS
# =============================================================================

from typing import TypeGuard


def is_string_list(val: list[object]) -> TypeGuard[list[str]]:
    """Type guard to narrow list type."""
    return all(isinstance(x, str) for x in val)


def process_items(items: list[object]) -> None:
    """Example using type guard."""
    if is_string_list(items):
        # Type checker knows items is list[str] here
        for item in items:
            print(item.upper())
    else:
        print("Not a string list")


# =============================================================================
# SELF TYPE (Python 3.11+)
# =============================================================================

from typing import Self


class Builder:
    """Class using Self type for method chaining."""
    
    def __init__(self) -> None:
        self._value: int = 0
    
    def add(self, n: int) -> Self:
        self._value += n
        return self
    
    def multiply(self, n: int) -> Self:
        self._value *= n
        return self
    
    def result(self) -> int:
        return self._value


class ExtendedBuilder(Builder):
    """Subclass inherits correct Self type."""
    
    def subtract(self, n: int) -> Self:
        self._value -= n
        return self


# Method chaining works correctly with types
result = Builder().add(5).multiply(2).result()
extended_result = ExtendedBuilder().add(10).subtract(3).multiply(2).result()


# =============================================================================
# DEMONSTRATIONS
# =============================================================================

if __name__ == "__main__":
    print("=" * 60)
    print("BASIC TYPE HINTS")
    print("=" * 60)
    print(f"greet('World'): {greet('World')}")
    print(f"add(5, 3): {add(5, 3)}")
    
    print("\n" + "=" * 60)
    print("GENERIC STACK")
    print("=" * 60)
    stack: Stack[int] = Stack()
    stack.push(1)
    stack.push(2)
    stack.push(3)
    print(f"Stack peek: {stack.peek()}")
    print(f"Stack pop: {stack.pop()}")
    
    print("\n" + "=" * 60)
    print("TYPED DICTIONARY")
    print("=" * 60)
    user: UserDict = {"name": "Alice", "age": 30, "email": "alice@example.com"}
    print(f"Process user: {process_user_data(user)}")
    
    print("\n" + "=" * 60)
    print("PROTOCOLS (DUCK TYPING)")
    print("=" * 60)
    shapes: list[Drawable] = [Circle(5), Square(10)]
    for shape in shapes:
        render(shape)
    
    print("\n" + "=" * 60)
    print("OVERLOADED FUNCTIONS")
    print("=" * 60)
    print(f"process(5): {process(5)}")
    print(f"process('hello'): {process('hello')}")
    print(f"process([1,2,3]): {process([1, 2, 3])}")
    
    print("\n" + "=" * 60)
    print("GENERIC REPOSITORY")
    print("=" * 60)
    repo = UserRepository()
    user_obj = User(0, "Bob", "bob@example.com")
    id = repo.save(user_obj)
    print(f"Saved user with ID: {id}")
    print(f"Retrieved user: {repo.get(id)}")
Examples - Python Tutorial | DeepML