- What are type hints ā a way to tell what type of data your code expects
- Why use them ā catch bugs early, better IDE help, clearer code
- Basic syntax ā how to add type hints to variables and functions
- Common types ā int, str, float, bool, None, and more
- Collections ā List, Tuple, Dict, Set
- Advanced ā Optional, Union, Any, TypeVar
- Real-world use ā practical examples you can use
What are Type Hints?
A type hint is a way to tell Python (and other programmers) what type of data a variable should hold or what a function should return.
Think of type hints like labels on boxes. If you have a box labeled "Books", you know what to expect inside. If someone gives you a box labeled "Books" and you open it to find shoes, you know something is wrong.
Type hints don't change how your code runs. They're just information for you, other developers, and your tools (like IDEs and type checkers).
š” Key concept: Type hints are like notes you leave for yourself and others. They say "this is what I expect" but Python doesn't enforce it at runtime.
Why Use Type Hints?
The Benefits of Type Hints
Here's why type hints make your life easier:
# Why Use Type Hints?
print("=" * 50)
print("WHY USE TYPE HINTS?")
print("=" * 50)
# ============================================================
# WITHOUT TYPE HINTS ā Confusing and Error-Prone
# ============================================================
print("\nā WITHOUT TYPE HINTS:")
def process_user_data(user):
"""What type is 'user'? What does this return?"""
# Is user a dict? A class? A list?
# We have to read the code to know!
return user.get("name", "Unknown")
# Anyone using this function has to guess what to pass
data = {"name": "Alice", "age": 30}
name = process_user_data(data)
print(f" Name: {name}")
# But what if someone passes a list?
bad_data = ["Alice", 30]
# This would crash! But we only know at runtime
# name = process_user_data(bad_data) # AttributeError: 'list' object has no attribute 'get'
# ============================================================
# WITH TYPE HINTS ā Clear and Safe
# ============================================================
print("\nā
WITH TYPE HINTS:")
from typing import Dict
def process_user_data_typed(user: Dict[str, str]) -> str:
"""Process user data and return the name.
Args:
user: A dictionary with at least a 'name' key
Returns:
The user's name as a string
"""
return user.get("name", "Unknown")
# Now it's clear what to pass!
typed_data: Dict[str, str] = {"name": "Alice", "age": "30"}
name = process_user_data_typed(typed_data)
print(f" Name: {name}")
# And tools like mypy would catch if you pass the wrong type
# ============================================================
# BENEFITS SUMMARY
# ============================================================
print("\n" + "-" * 30)
print("BENEFITS OF TYPE HINTS")
print("-" * 30)
print("""
āāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā BENEFIT ā WHAT IT MEANS FOR YOU ā
āāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Better IDE Support ā Autocomplete shows expected types ā
ā ā ā
ā Early Bug Detection ā Tools like mypy catch type errors ā
ā ā before running code ā
ā ā ā
ā Clearer Code ā Anyone reading your code knows what ā
ā ā to expect ā
ā ā ā
ā Better Documentation ā Type hints act as built-in documentation ā
ā ā ā
ā Easier Refactoring ā Changes are easier to make safely ā
ā ā ā
ā More Maintainable ā Teams can work together more easily ā
āāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
""")
Benefits of type hints:
- Better IDE support ā autocomplete and error highlighting
- Early bug detection ā catch errors before running
- Clearer code ā everyone knows what's expected
- Better documentation ā type hints explain the code
- Easier refactoring ā changes are safer
Quick Check: Do type hints change how your code runs? (Answer: No ā they're just information for developers and tools)
Basic Syntax
How to Write Type Hints
Adding type hints is easy. You just add : type after variable names and -> type after function definitions.
# Basic Type Hint Syntax
print("=" * 50)
print("BASIC SYNTAX")
print("=" * 50)
# ============================================================
# VARIABLES
# ============================================================
print("\n1. VARIABLE TYPE HINTS")
# Without type hint (normal Python)
name = "Alice"
age = 30
price = 19.99
is_active = True
# With type hint (adds clarity)
name: str = "Alice"
age: int = 30
price: float = 19.99
is_active: bool = True
# You can also declare without assigning
user_id: int
user_id = 123
print(f" name: {name} (type hint: str)")
print(f" age: {age} (type hint: int)")
print(f" price: {price} (type hint: float)")
print(f" is_active: {is_active} (type hint: bool)")
# ============================================================
# FUNCTIONS
# ============================================================
print("\n2. FUNCTION TYPE HINTS")
# Without type hints
def add(a, b):
return a + b
# With type hints (clear what's expected)
def add_typed(a: int, b: int) -> int:
"""Add two numbers and return the result"""
return a + b
print(f" add_typed(5, 3) -> {add_typed(5, 3)}")
# ============================================================
# RETURNING DIFFERENT TYPES
# ============================================================
print("\n3. RETURNING DIFFERENT TYPES")
def greet(name: str, formal: bool = False) -> str:
"""Return a greeting based on formality"""
if formal:
return f"Good day, {name}!"
return f"Hello, {name}!"
print(f" greet('Alice') -> {greet('Alice')}")
print(f" greet('Bob', True) -> {greet('Bob', True)}")
# ============================================================
# NO RETURN VALUE (None)
# ============================================================
print("\n4. FUNCTIONS THAT RETURN NOTHING")
def log_message(message: str) -> None:
"""Log a message (returns nothing)"""
print(f" LOG: {message}")
log_message("System started")
# Returns None
# ============================================================
# COMPLEX TYPES
# ============================================================
print("\n5. MORE COMPLEX EXAMPLES")
from typing import List, Dict, Tuple
# Function that takes a list and returns a list
def double_numbers(numbers: List[int]) -> List[int]:
return [n * 2 for n in numbers]
# Function that takes a dict and returns a tuple
def get_user_info(user: Dict[str, str]) -> Tuple[str, str]:
return (user.get("name", "Unknown"), user.get("email", "unknown@example.com"))
numbers = [1, 2, 3, 4, 5]
doubled = double_numbers(numbers)
print(f" double_numbers({numbers}) -> {doubled}")
user = {"name": "Alice", "email": "alice@example.com"}
name, email = get_user_info(user)
print(f" get_user_info({user}) -> ({name}, {email})")
Basic syntax key points:
- Variable hint ā
name: str = "Alice" - Parameter hint ā
def greet(name: str): - Return hint ā
def greet() -> str: - No return ā
-> Nonefor functions that return nothing - Type hints are optional ā you can add them gradually
Quick Check: How do you add a type hint to a function parameter? (Answer: def function(param: type):)
Common Types
Basic Type Hints You'll Use Every Day
Here are the most common types you'll use with type hints:
# Common Types in Type Hints
print("=" * 50)
print("COMMON TYPES")
print("=" * 50)
# ============================================================
# BASIC TYPES
# ============================================================
print("\n1. BASIC TYPES")
# int ā whole numbers
age: int = 25
print(f" age: {age} (int)")
# str ā text
name: str = "Alice"
print(f" name: {name} (str)")
# float ā decimal numbers
price: float = 19.99
print(f" price: {price} (float)")
# bool ā True or False
is_active: bool = True
print(f" is_active: {is_active} (bool)")
# None ā no value (use NoneType or Optional)
result: None = None
print(f" result: {result} (None)")
# ============================================================
# COLLECTION TYPES (need typing module)
# ============================================================
print("\n2. COLLECTION TYPES")
from typing import List, Tuple, Dict, Set
# List ā list of items
numbers: List[int] = [1, 2, 3, 4, 5]
print(f" numbers: {numbers} (List[int])")
# Tuple ā fixed-length collection
person: Tuple[str, int] = ("Alice", 30)
print(f" person: {person} (Tuple[str, int])")
# Dict ā key-value pairs
user: Dict[str, str] = {"name": "Alice", "email": "alice@example.com"}
print(f" user: {user} (Dict[str, str])")
# Set ā unique items
tags: Set[str] = {"python", "programming", "tutorial"}
print(f" tags: {tags} (Set[str])")
# ============================================================
# NESTED TYPES
# ============================================================
print("\n3. NESTED TYPES")
# List of lists (matrix)
matrix: List[List[int]] = [[1, 2], [3, 4], [5, 6]]
print(f" matrix: {matrix} (List[List[int]])")
# Dictionary with list values
data: Dict[str, List[int]] = {"scores": [95, 87, 92]}
print(f" data: {data} (Dict[str, List[int]])")
# Tuple with different types
student: Tuple[str, int, List[str]] = ("Bob", 20, ["Math", "Science"])
print(f" student: {student} (Tuple[str, int, List[str]])")
# ============================================================
# TYPE ALIASES (make code cleaner)
# ============================================================
print("\n4. TYPE ALIASES")
# Give a complex type a simple name
Person = Tuple[str, int, str] # name, age, email
people: List[Person] = [
("Alice", 30, "alice@example.com"),
("Bob", 25, "bob@example.com")
]
print(f" people: {people} (List[Person])")
# Another alias
Employee = Dict[str, str | int] # Python 3.10+ union syntax
employees: List[Employee] = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
]
print(f" employees: {employees} (List[Employee])")
# ============================================================
# QUICK REFERENCE
# ============================================================
print("\n" + "-" * 30)
print("QUICK REFERENCE")
print("-" * 30)
print("""
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā TYPE ā HINT ā
āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā int ā age: int = 25 ā
ā str ā name: str = "Alice" ā
ā float ā price: float = 19.99 ā
ā bool ā active: bool = True ā
ā List[int] ā numbers: List[int] = [1, 2, 3] ā
ā Tuple[str, int] ā person: Tuple[str, int] = ("Alice", 30) ā
ā Dict[str, str] ā user: Dict[str, str] = {"key": "value"} ā
ā Set[str] ā tags: Set[str] = {"a", "b"} ā
ā List[List[int]] ā matrix: List[List[int]] = [[1, 2], [3, 4]] ā
ā None ā result: None = None ā
āāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
""")
Common types key points:
- Basic types ā int, str, float, bool, None
- Collections ā List, Tuple, Dict, Set (from typing module)
- Nested types ā List[List[int]], Dict[str, List[int]]
- Type aliases ā give complex types simple names
Quick Check: What module do you need to import for List, Dict, and Tuple? (Answer: from typing import List, Dict, Tuple)
Type Hints for Collections
Working with Lists, Tuples, Dictionaries, and Sets
Collections are everywhere in Python. Here's how to add type hints to them.
# Type Hints for Collections
print("=" * 50)
print("COLLECTION TYPE HINTS")
print("=" * 50)
from typing import List, Tuple, Dict, Set, Any
# ============================================================
# LISTS
# ============================================================
print("\n1. LISTS")
# List of strings
names: List[str] = ["Alice", "Bob", "Charlie"]
print(f" names: {names}")
# List of integers
scores: List[int] = [95, 87, 92, 78]
print(f" scores: {scores}")
# List of mixed types (use Any or Union)
mixed: List[Any] = ["Alice", 30, 19.99, True]
print(f" mixed: {mixed}")
# Function that works with lists
def get_average(numbers: List[float]) -> float:
return sum(numbers) / len(numbers)
avg = get_average([10.5, 20.5, 30.5])
print(f" Average: {avg:.2f}")
# ============================================================
# TUPLES
# ============================================================
print("\n2. TUPLES")
# Fixed-size tuple
person: Tuple[str, int] = ("Alice", 30)
print(f" person: {person}")
# Tuple with more items
employee: Tuple[str, int, str] = ("Bob", 25, "Engineer")
print(f" employee: {employee}")
# Empty tuple
empty: Tuple[()] = ()
print(f" empty: {empty}")
# Variable-length tuple (any number of strings)
tags: Tuple[str, ...] = ("python", "programming", "tutorial", "tips")
print(f" tags: {tags}")
# ============================================================
# DICTIONARIES
# ============================================================
print("\n3. DICTIONARIES")
# String keys, string values
user: Dict[str, str] = {"name": "Alice", "email": "alice@example.com"}
print(f" user: {user}")
# String keys, int values
scores_dict: Dict[str, int] = {"Alice": 95, "Bob": 87, "Charlie": 92}
print(f" scores_dict: {scores_dict}")
# String keys, mixed values
profile: Dict[str, Any] = {"name": "Alice", "age": 30, "active": True}
print(f" profile: {profile}")
# Function that works with dictionaries
def get_user_name(user: Dict[str, str]) -> str:
return user.get("name", "Unknown")
print(f" get_user_name: {get_user_name(user)}")
# ============================================================
# SETS
# ============================================================
print("\n4. SETS")
# Set of strings
tags_set: Set[str] = {"python", "programming", "tutorial"}
print(f" tags_set: {tags_set}")
# Set of integers
numbers_set: Set[int] = {1, 2, 3, 4, 5}
print(f" numbers_set: {numbers_set}")
# Function that works with sets
def has_tag(tags: Set[str], tag: str) -> bool:
return tag in tags
print(f" has_tag: {has_tag(tags_set, 'python')}")
# ============================================================
# PRACTICAL EXAMPLES
# ============================================================
print("\n5. PRACTICAL EXAMPLES")
# Process a list of dictionaries
def process_users(users: List[Dict[str, Any]]) -> List[str]:
"""Extract names from a list of user dictionaries"""
names = []
for user in users:
if "name" in user:
names.append(user["name"])
return names
users_list: List[Dict[str, Any]] = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Charlie", "age": 35}
]
result = process_users(users_list)
print(f" Users: {users_list}")
print(f" Names: {result}")
# Group scores by subject
def group_scores(scores: List[Tuple[str, int]]) -> Dict[str, List[int]]:
"""Group scores by subject"""
grouped: Dict[str, List[int]] = {}
for subject, score in scores:
if subject not in grouped:
grouped[subject] = []
grouped[subject].append(score)
return grouped
scores_list: List[Tuple[str, int]] = [
("Math", 95), ("Science", 88), ("Math", 92), ("Science", 90)
]
grouped = group_scores(scores_list)
print(f" Grouped scores: {grouped}")
Collection type hints key points:
- List ā
List[str]for a list of strings - Tuple ā
Tuple[str, int]for a fixed-size tuple - Dict ā
Dict[str, int]for string keys and int values - Set ā
Set[str]for a set of strings - Nested ā
List[Dict[str, Any]]for complex structures
Quick Check: How do you type hint a dictionary with string keys and integer values? (Answer: Dict[str, int])
Advanced Type Hints
Optional, Union, Any, and More
Sometimes a variable can be one of several types, or it might be missing. Here's how to handle those situations.
# Advanced Type Hints
print("=" * 50)
print("ADVANCED TYPE HINTS")
print("=" * 50)
from typing import Optional, Union, Any, TypeVar, Callable
from typing import List, Dict
# ============================================================
# Optional ā Could be None
# ============================================================
print("\n1. OPTIONAL ā Maybe None")
# Before: using Union
def get_user_old(user_id: int) -> Union[str, None]:
"""Return user name or None if not found"""
if user_id == 1:
return "Alice"
return None
# Better: using Optional
def get_user(user_id: int) -> Optional[str]:
"""Return user name or None if not found"""
if user_id == 1:
return "Alice"
return None
# Optional is just a shorthand for Union[T, None]
print(f" get_user(1): {get_user(1)}")
print(f" get_user(2): {get_user(2)}")
# Optional with default values
def greet_user(name: Optional[str] = None) -> str:
if name:
return f"Hello, {name}!"
return "Hello, stranger!"
print(f" greet_user('Alice'): {greet_user('Alice')}")
print(f" greet_user(): {greet_user()}")
# ============================================================
# Union ā Multiple Possible Types
# ============================================================
print("\n2. UNION ā Multiple Types")
# Before Python 3.10 (using Union from typing)
def process_value_old(value: Union[int, str]) -> str:
return f"Value: {value}"
# Python 3.10+ (using | syntax)
def process_value(value: int | str) -> str:
return f"Value: {value}"
print(f" process_value(5): {process_value(5)}")
print(f" process_value('hello'): {process_value('hello')}")
# More complex union
def handle_data(data: int | str | List[int]) -> str:
if isinstance(data, int):
return f"Integer: {data}"
elif isinstance(data, str):
return f"String: {data}"
elif isinstance(data, list):
return f"List: {data}"
return "Unknown"
print(f" handle_data(10): {handle_data(10)}")
print(f" handle_data('test'): {handle_data('test')}")
print(f" handle_data([1, 2, 3]): {handle_data([1, 2, 3])}")
# ============================================================
# Any ā Any Type (use sparingly)
# ============================================================
print("\n3. ANY ā Any Type")
from typing import Any
def log_data(data: Any) -> None:
"""Log any type of data"""
print(f" Log: {data}")
# Any can be anything
log_data("Hello")
log_data(123)
log_data([1, 2, 3])
log_data({"name": "Alice"})
print(" ā ļø Use Any sparingly ā it defeats the purpose of type hints!")
# ============================================================
# TypeVar ā Generic Types
# ============================================================
print("\n4. TYPEVAR ā Generic Types")
from typing import TypeVar
T = TypeVar('T') # A generic type variable
def first_item(items: List[T]) -> T:
"""Return the first item from any list"""
return items[0] if items else None
# Works with any type
print(f" first_item([1, 2, 3]): {first_item([1, 2, 3])}")
print(f" first_item(['a', 'b', 'c']): {first_item(['a', 'b', 'c'])}")
# ============================================================
# Callable ā Function Types
# ============================================================
print("\n5. CALLABLE ā Function Types")
from typing import Callable
def apply_operation(x: int, y: int, operation: Callable[[int, int], int]) -> int:
"""Apply an operation to two numbers"""
return operation(x, y)
def add(a: int, b: int) -> int:
return a + b
def multiply(a: int, b: int) -> int:
return a * b
print(f" apply_operation(5, 3, add): {apply_operation(5, 3, add)}")
print(f" apply_operation(5, 3, multiply): {apply_operation(5, 3, multiply)}")
# ============================================================
# SUMMARY
# ============================================================
print("\n" + "-" * 30)
print("SUMMARY ā ADVANCED TYPES")
print("-" * 30)
print("""
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā TYPE ā WHAT IT MEANS ā
āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Optional[T] ā Could be T or None ā
ā Union[T, U] ā Could be T or U ā
ā T | U ā Same as Union (Python 3.10+) ā
ā Any ā Any type (use sparingly) ā
ā TypeVar('T') ā Generic type (works with any type) ā
ā Callable ā A function type ā
āāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
""")
Advanced type hints key points:
- Optional ā
Optional[str]means string or None - Union ā
Union[int, str]means int or string - Any ā
Anymeans any type (use sparingly) - TypeVar ā
T = TypeVar('T')for generic types - Callable ā
Callable[[int, int], int]for functions
Quick Check: When should you use Optional? (Answer: When a variable can be None in addition to its main type)
Real-World Example
Building a Data Processing Pipeline
# Real-World Example: Data Processing Pipeline
from typing import List, Dict, Optional, Tuple, Any
import json
from datetime import datetime
print("=" * 60)
print("DATA PROCESSING PIPELINE")
print("=" * 60)
# ============================================================
# DATA MODELS (with type hints)
# ============================================================
class DataProcessor:
"""Process data with clear type hints"""
def __init__(self, config: Dict[str, Any]) -> None:
"""Initialize the processor with configuration"""
self.config = config
self.processed_count: int = 0
self.errors: List[str] = []
def load_data(self, file_path: str) -> List[Dict[str, Any]]:
"""Load data from a JSON file"""
try:
with open(file_path, 'r') as f:
data = json.load(f)
return data if isinstance(data, list) else []
except (FileNotFoundError, json.JSONDecodeError) as e:
self.errors.append(f"Error loading data: {e}")
return []
def filter_data(self, data: List[Dict[str, Any]], field: str, value: Any) -> List[Dict[str, Any]]:
"""Filter data by a specific field value"""
return [item for item in data if item.get(field) == value]
def transform_data(self, data: List[Dict[str, Any]]) -> List[Dict[str, str]]:
"""Transform data to a standard format"""
result: List[Dict[str, str]] = []
for item in data:
transformed = {
"id": str(item.get("id", "")),
"name": item.get("name", "Unknown"),
"email": item.get("email", "unknown@example.com"),
"processed_at": datetime.now().isoformat()
}
result.append(transformed)
return result
def save_data(self, data: List[Dict[str, str]], output_path: str) -> bool:
"""Save processed data to a file"""
try:
with open(output_path, 'w') as f:
json.dump(data, f, indent=2)
return True
except Exception as e:
self.errors.append(f"Error saving data: {e}")
return False
def process(self, input_file: str, output_file: str) -> Tuple[bool, int]:
"""Run the complete processing pipeline"""
print(f" š Processing: {input_file}")
# Step 1: Load data
raw_data = self.load_data(input_file)
if not raw_data:
return (False, 0)
# Step 2: Filter data (optional)
filter_field = self.config.get("filter_field")
filter_value = self.config.get("filter_value")
if filter_field and filter_value is not None:
raw_data = self.filter_data(raw_data, filter_field, filter_value)
# Step 3: Transform data
processed_data = self.transform_data(raw_data)
# Step 4: Save data
success = self.save_data(processed_data, output_file)
self.processed_count = len(processed_data)
return (success, self.processed_count)
def get_stats(self) -> Dict[str, Any]:
"""Get processing statistics"""
return {
"processed_count": self.processed_count,
"error_count": len(self.errors),
"last_error": self.errors[-1] if self.errors else None
}
# ============================================================
# SIMULATED DATA
# ============================================================
def create_sample_data() -> List[Dict[str, Any]]:
"""Create sample data for testing"""
return [
{"id": 1, "name": "Alice", "email": "alice@example.com", "status": "active"},
{"id": 2, "name": "Bob", "email": "bob@example.com", "status": "active"},
{"id": 3, "name": "Charlie", "email": "charlie@example.com", "status": "inactive"},
{"id": 4, "name": "Diana", "email": "diana@example.com", "status": "active"}
]
# ============================================================
# DEMONSTRATION
# ============================================================
print("\n1. CREATING PROCESSOR")
config: Dict[str, Any] = {
"filter_field": "status",
"filter_value": "active"
}
processor = DataProcessor(config)
print(f" Config: {config}")
print("\n2. SIMULATING DATA PROCESSING")
# In real use, you'd read from a file
# For demo, we'll use sample data
sample_data = create_sample_data()
print(f" Sample data: {sample_data}")
print("\n3. FILTERING DATA")
active_users = processor.filter_data(sample_data, "status", "active")
print(f" Active users: {active_users}")
print("\n4. TRANSFORMING DATA")
transformed = processor.transform_data(active_users)
print(f" Transformed data: {transformed}")
print("\n5. GETTING STATS")
stats = processor.get_stats()
print(f" Stats: {stats}")
print("\n" + "=" * 60)
print("KEY TAKEAWAYS:")
print("=" * 60)
print("ā
Type hints make the code self-documenting")
print("ā
Functions clearly show what they expect and return")
print("ā
Complex types are easy to understand")
print("ā
IDEs provide better autocomplete and error checking")
print("ā
The code is easier to maintain and debug")
Real-world example key points:
- Clear function signatures ā know what each function expects
- Complex types ā
List[Dict[str, Any]]makes it clear - Return types ā
Tuple[bool, int]shows what's returned - Self-documenting ā type hints explain the code
- Better tools ā IDEs and type checkers work better
Quick Check: What does List[Dict[str, Any]] mean? (Answer: A list of dictionaries where keys are strings and values can be any type)
Best Practices
Using Type Hints Effectively
# Best Practices for Type Hints
print("=" * 60)
print("BEST PRACTICES FOR TYPE HINTS")
print("=" * 60)
from typing import List, Dict, Optional, Any
# ============================================================
# 1. BE SPECIFIC (AVOID ANY)
# ============================================================
print("\n1. BE SPECIFIC ā AVOID ANY")
# ā BAD: Using Any when you could be specific
def process_data_bad(data: Any) -> Any:
return data
# ā
GOOD: Be specific about what you expect
def process_data_good(data: List[Dict[str, str]]) -> Dict[str, List[str]]:
result: Dict[str, List[str]] = {}
for item in data:
for key, value in item.items():
result.setdefault(key, []).append(value)
return result
print(" ā
Specific types make code clearer and safer")
# ============================================================
# 2. USE OPTIONAL FOR VALUES THAT CAN BE NONE
# ============================================================
print("\n2. USE OPTIONAL FOR NONE VALUES")
# ā BAD: Using Union
def find_user_bad(user_id: int) -> Union[Dict, None]:
return None
# ā
GOOD: Using Optional (clearer intention)
def find_user_good(user_id: int) -> Optional[Dict[str, str]]:
# Returns dict or None
return None
print(" ā
Optional[T] = Union[T, None] but clearer")
# ============================================================
# 3. USE TYPE ALIASES FOR COMPLEX TYPES
# ============================================================
print("\n3. USE TYPE ALIASES FOR COMPLEX TYPES")
# ā BAD: Repeating complex types
def process_user_data(data: List[Dict[str, Union[str, int, float]]]) -> None:
pass
def analyze_user_data(data: List[Dict[str, Union[str, int, float]]]) -> Dict:
pass
# ā
GOOD: Create a type alias
from typing import TypeAlias
UserData: TypeAlias = List[Dict[str, Union[str, int, float]]]
def process_user_data_alias(data: UserData) -> None:
pass
def analyze_user_data_alias(data: UserData) -> Dict:
pass
print(" ā
Type aliases make code cleaner and easier to change")
# ============================================================
# 4. USE TYPE HINTS IN CLASSES
# ============================================================
print("\n4. USE TYPE HINTS IN CLASSES")
class User:
"""User class with type hints"""
def __init__(self, name: str, age: int, email: Optional[str] = None) -> None:
self.name: str = name
self.age: int = age
self.email: Optional[str] = email
self.is_active: bool = True
self.friends: List[str] = []
def get_info(self) -> Dict[str, Any]:
return {
"name": self.name,
"age": self.age,
"email": self.email,
"is_active": self.is_active,
"friends": self.friends
}
def add_friend(self, friend_name: str) -> None:
self.friends.append(friend_name)
user = User("Alice", 30)
user.add_friend("Bob")
print(f" User: {user.get_info()}")
# ============================================================
# 5. USE MYPY FOR TYPE CHECKING
# ============================================================
print("\n5. USE MYPY FOR TYPE CHECKING")
print(" ā
Install mypy: pip install mypy")
print(" ā
Run mypy: mypy your_file.py")
print(" ā
mypy will catch type errors before you run")
# ============================================================
# 6. START SMALL
# ============================================================
print("\n6. START SMALL")
print("""
ā
Don't try to add type hints to everything at once
ā
Start with new code or code you're changing
ā
Gradually add hints to existing code
ā
Focus on: function parameters, return values, and public APIs
""")
# ============================================================
# SUMMARY
# ============================================================
print("\n" + "=" * 60)
print("BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā PRACTICE ā WHY IT MATTERS ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Be specific, avoid Any ā Makes code clearer and safer ā
ā ā ā
ā Use Optional for None ā Clearly shows when None is possible ā
ā ā ā
ā Use type aliases ā Simplifies complex types ā
ā ā ā
ā Use hints in classes ā Makes OOP code more readable ā
ā ā ā
ā Use mypy for checking ā Catches errors early ā
ā ā ā
ā Start small ā Don't overwhelm yourself ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
š REMEMBER:
⢠Type hints are optional but helpful
⢠They don't change how your code runs
⢠They make your code better for others and for yourself
⢠Use tools like mypy to check your types
""")
Best practices summary:
- Be specific ā avoid
Anywhen possible - Use Optional ā when values can be
None - Use type aliases ā simplify complex types
- Use hints in classes ā makes OOP code clearer
- Use mypy ā catch type errors early
- Start small ā add hints gradually
Quick Check: What tool can you use to check type hints? (Answer: mypy)
Try It Yourself
Experiment with type hints in the editor below.
TYPE HINTS - PRACTICE
==================================================
1. BASIC TYPE HINTS
add_numbers(5, 3): 8
greet_user('Alice'): Hello, Alice!
is_adult(20): True
is_adult(16): False
2. COLLECTION TYPE HINTS
get_averages([85, 90, 78, 92, 88]): 86.60
get_user_names([{'name': 'Alice', 'email': 'alice@example.com'}, {'name': 'Bob', 'email': 'bob@example.com'}]): ['Alice', 'Bob']
3. OPTIONAL AND UNION
find_user(1): Alice
find_user(99): None
process_value(10): Processed: 10
process_value('hello'): Processed: hello
4. COMPLEX TYPES
Student: ('Alice', 20, ['Math', 'Science', 'English'])
Info: {'name': 'Alice', 'age': 20, 'subject_count': 3, 'subjects': ['Math', 'Science', 'English']}
You've Got It!
You now understand type hints in Python. You know how to add them to variables, functions, and classes, and you understand advanced types like Optional and Union.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What are type hints in Python?
Do type hints affect how my code runs?
Do I need to use type hints?
What's the difference between Optional and Union?
Optional[T] is a shorthand for Union[T, None]. It means the value can be type T or None. Union[T, U] means the value can be type T or type U.
Can I use type hints with classes?
What's the difference between Python 3.10 union syntax and typing.Union?
| syntax for unions (e.g., int | str). This is simpler and cleaner than Union[int, str]. Both work, but the | syntax is newer and preferred in Python 3.10+.
Where to Go From Here
Now that you understand type hints in Python, check out these related topics:
Dataclasses
Learn how dataclasses work with type hints.
Learn More āDecorators
Learn about decorators ā another way to enhance functions.
Learn More āAsync/Await
Learn about asynchronous programming with type hints.
Learn More ā