01_type_hints.py

Download
python 290 lines 7.2 KB
  1"""
  2Type Hints and Annotations in Python
  3
  4Demonstrates:
  5- Basic type annotations
  6- Generic types (List, Dict, Tuple)
  7- Union and Optional
  8- TypeVar and Generic classes
  9- Protocol (structural subtyping)
 10- Runtime type checking
 11"""
 12
 13from typing import (
 14    List, Dict, Tuple, Union, Optional, TypeVar, Generic, Protocol, Callable
 15)
 16from typing import get_type_hints, get_args, get_origin
 17
 18
 19def section(title: str) -> None:
 20    """Print a section header."""
 21    print("\n" + "=" * 60)
 22    print(f"  {title}")
 23    print("=" * 60)
 24
 25
 26# =============================================================================
 27# Basic Type Annotations
 28# =============================================================================
 29
 30section("Basic Type Annotations")
 31
 32
 33def greet(name: str, age: int) -> str:
 34    """Function with basic type hints."""
 35    return f"Hello {name}, you are {age} years old"
 36
 37
 38result = greet("Alice", 30)
 39print(f"greet('Alice', 30) -> {result}")
 40print(f"Function annotations: {greet.__annotations__}")
 41
 42
 43# =============================================================================
 44# Collection Types
 45# =============================================================================
 46
 47section("Collection Types")
 48
 49
 50def process_scores(scores: List[int]) -> Dict[str, float]:
 51    """Process a list of scores and return statistics."""
 52    return {
 53        "mean": sum(scores) / len(scores) if scores else 0,
 54        "max": max(scores) if scores else 0,
 55        "min": min(scores) if scores else 0
 56    }
 57
 58
 59scores = [85, 92, 78, 95, 88]
 60stats = process_scores(scores)
 61print(f"Scores: {scores}")
 62print(f"Statistics: {stats}")
 63
 64
 65def coordinates() -> Tuple[float, float, float]:
 66    """Return 3D coordinates."""
 67    return (1.5, 2.7, 3.9)
 68
 69
 70coords = coordinates()
 71print(f"Coordinates: {coords}")
 72
 73
 74# =============================================================================
 75# Union and Optional
 76# =============================================================================
 77
 78section("Union and Optional")
 79
 80
 81def process_id(user_id: Union[int, str]) -> str:
 82    """Accept either int or str ID."""
 83    return f"Processing ID: {user_id} (type: {type(user_id).__name__})"
 84
 85
 86print(process_id(12345))
 87print(process_id("USER_789"))
 88
 89
 90def find_user(user_id: int) -> Optional[Dict[str, str]]:
 91    """Return user dict or None if not found."""
 92    users = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
 93    return users.get(user_id)
 94
 95
 96user = find_user(1)
 97print(f"find_user(1): {user}")
 98user = find_user(99)
 99print(f"find_user(99): {user}")
100
101
102# =============================================================================
103# TypeVar and Generics
104# =============================================================================
105
106section("TypeVar and Generics")
107
108T = TypeVar('T')
109
110
111def first_element(items: List[T]) -> Optional[T]:
112    """Return first element of list, preserving type."""
113    return items[0] if items else None
114
115
116int_list = [1, 2, 3]
117str_list = ["a", "b", "c"]
118print(f"first_element({int_list}) -> {first_element(int_list)}")
119print(f"first_element({str_list}) -> {first_element(str_list)}")
120
121
122class Stack(Generic[T]):
123    """Generic stack implementation."""
124
125    def __init__(self) -> None:
126        self._items: List[T] = []
127
128    def push(self, item: T) -> None:
129        self._items.append(item)
130
131    def pop(self) -> Optional[T]:
132        return self._items.pop() if self._items else None
133
134    def __repr__(self) -> str:
135        return f"Stack({self._items})"
136
137
138int_stack: Stack[int] = Stack()
139int_stack.push(10)
140int_stack.push(20)
141print(f"int_stack: {int_stack}")
142print(f"Popped: {int_stack.pop()}")
143
144str_stack: Stack[str] = Stack()
145str_stack.push("hello")
146str_stack.push("world")
147print(f"str_stack: {str_stack}")
148
149
150# =============================================================================
151# Protocol (Structural Subtyping)
152# =============================================================================
153
154section("Protocol (Structural Subtyping)")
155
156
157class Drawable(Protocol):
158    """Protocol for drawable objects."""
159
160    def draw(self) -> str:
161        ...
162
163
164class Circle:
165    """Circle - implements Drawable protocol without inheritance."""
166
167    def __init__(self, radius: float):
168        self.radius = radius
169
170    def draw(self) -> str:
171        return f"Drawing circle with radius {self.radius}"
172
173
174class Square:
175    """Square - also implements Drawable protocol."""
176
177    def __init__(self, side: float):
178        self.side = side
179
180    def draw(self) -> str:
181        return f"Drawing square with side {self.side}"
182
183
184def render(obj: Drawable) -> None:
185    """Render any drawable object."""
186    print(obj.draw())
187
188
189circle = Circle(5.0)
190square = Square(10.0)
191render(circle)
192render(square)
193
194
195# =============================================================================
196# Callable Types
197# =============================================================================
198
199section("Callable Types")
200
201
202def apply_operation(x: int, y: int, operation: Callable[[int, int], int]) -> int:
203    """Apply a binary operation to two integers."""
204    return operation(x, y)
205
206
207def add(a: int, b: int) -> int:
208    return a + b
209
210
211def multiply(a: int, b: int) -> int:
212    return a * b
213
214
215result1 = apply_operation(5, 3, add)
216result2 = apply_operation(5, 3, multiply)
217print(f"apply_operation(5, 3, add) -> {result1}")
218print(f"apply_operation(5, 3, multiply) -> {result2}")
219
220
221# =============================================================================
222# Runtime Type Checking
223# =============================================================================
224
225section("Runtime Type Checking with isinstance")
226
227
228def process_value(value: Union[int, str, List[int]]) -> str:
229    """Process different types at runtime."""
230    if isinstance(value, int):
231        return f"Integer: {value * 2}"
232    elif isinstance(value, str):
233        return f"String: {value.upper()}"
234    elif isinstance(value, list):
235        return f"List sum: {sum(value)}"
236    else:
237        return "Unknown type"
238
239
240print(process_value(42))
241print(process_value("hello"))
242print(process_value([1, 2, 3, 4, 5]))
243
244
245# =============================================================================
246# Type Introspection
247# =============================================================================
248
249section("Type Introspection")
250
251
252def example_function(x: int, y: str = "default") -> bool:
253    """Example function for introspection."""
254    return True
255
256
257hints = get_type_hints(example_function)
258print(f"Type hints for example_function: {hints}")
259
260# Introspect Union type
261union_type = Union[int, str]
262print(f"\nUnion[int, str]:")
263print(f"  Origin: {get_origin(union_type)}")
264print(f"  Args: {get_args(union_type)}")
265
266# Introspect List type
267list_type = List[int]
268print(f"\nList[int]:")
269print(f"  Origin: {get_origin(list_type)}")
270print(f"  Args: {get_args(list_type)}")
271
272
273# =============================================================================
274# Summary
275# =============================================================================
276
277section("Summary")
278
279print("""
280Type hints provide several benefits:
2811. Better IDE support (autocomplete, refactoring)
2822. Early error detection with static type checkers (mypy, pyright)
2833. Self-documenting code
2844. Runtime type checking with isinstance()
2855. Generic programming with TypeVar and Generic
286
287Note: Type hints are optional and not enforced at runtime
288unless you explicitly check them.
289""")