Prerequisites
- Basic understanding of programming concepts ๐
- Python installation (3.8+) ๐
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write clean, Pythonic code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on TypedDict! ๐ In this guide, weโll explore how to bring type safety to Python dictionaries, making your code more robust and maintainable.
Youโll discover how TypedDict can transform your Python development experience. Whether youโre building web applications ๐, APIs ๐ฅ๏ธ, or data processing pipelines ๐, understanding TypedDict is essential for writing type-safe, self-documenting code.
By the end of this tutorial, youโll feel confident using TypedDict in your own projects! Letโs dive in! ๐โโ๏ธ
๐ Understanding TypedDict
๐ค What is TypedDict?
TypedDict is like a blueprint for dictionaries ๐๏ธ. Think of it as a contract that specifies exactly what keys a dictionary should have and what types their values should be - like a form that tells you which fields to fill out and what kind of information goes in each field!
In Python terms, TypedDict provides static type hints for dictionaries with a fixed schema. This means you can:
- โจ Define the exact structure of your dictionaries
- ๐ Get IDE autocomplete and type checking
- ๐ก๏ธ Catch type errors before runtime
๐ก Why Use TypedDict?
Hereโs why developers love TypedDict:
- Type Safety ๐: Catch errors before they happen
- Better IDE Support ๐ป: Autocomplete dictionary keys
- Code Documentation ๐: Types serve as inline docs
- Refactoring Confidence ๐ง: Change code without fear
Real-world example: Imagine building a user management system ๐ฅ. With TypedDict, you can ensure every user dictionary has the required fields with correct types!
๐ง Basic Syntax and Usage
๐ Simple Example
Letโs start with a friendly example:
# ๐ Hello, TypedDict!
from typing import TypedDict
# ๐จ Creating a simple TypedDict
class Person(TypedDict):
name: str # ๐ค Person's name
age: int # ๐ Person's age
email: str # ๐ง Email address
# ๐๏ธ Using our TypedDict
developer: Person = {
"name": "Sarah",
"age": 28,
"email": "[email protected]"
}
# โจ IDE now knows the structure!
print(f"Hello {developer['name']}! ๐")
๐ก Explanation: TypedDict creates a type annotation for dictionaries. The IDE now knows exactly what keys are available!
๐ฏ Common Patterns
Here are patterns youโll use daily:
from typing import TypedDict, Optional, List
# ๐๏ธ Pattern 1: Optional fields
class User(TypedDict, total=False):
id: str
name: str
bio: Optional[str] # ๐ฏ Optional field
hobbies: List[str] # ๐ List of hobbies
# ๐จ Pattern 2: Required and optional mix
class Product(TypedDict):
id: str # Required
name: str # Required
price: float # Required
class ProductOptional(TypedDict, total=False):
description: str # Optional
discount: float # Optional
# ๐ Pattern 3: Combining TypedDicts
class FullProduct(Product, ProductOptional):
pass # Has both required and optional fields!
๐ก Practical Examples
๐ Example 1: E-commerce Order System
Letโs build something real:
# ๐๏ธ E-commerce order system
from typing import TypedDict, List, Literal
from datetime import datetime
class Address(TypedDict):
street: str
city: str
zip_code: str
country: str
class OrderItem(TypedDict):
product_id: str
name: str
quantity: int
price: float
emoji: str # Every product needs an emoji! ๐จ
class Order(TypedDict):
order_id: str
customer_name: str
items: List[OrderItem]
shipping_address: Address
status: Literal["pending", "shipped", "delivered"]
total: float
# ๐ Order processing class
class OrderProcessor:
def __init__(self):
self.orders: List[Order] = []
# โ Create new order
def create_order(self, customer: str, address: Address) -> Order:
order: Order = {
"order_id": f"ORD-{len(self.orders) + 1:04d}",
"customer_name": customer,
"items": [],
"shipping_address": address,
"status": "pending",
"total": 0.0
}
self.orders.append(order)
print(f"๐ฆ Created order for {customer}!")
return order
# ๐๏ธ Add item to order
def add_item(self, order: Order, item: OrderItem) -> None:
order["items"].append(item)
order["total"] += item["price"] * item["quantity"]
print(f"Added {item['emoji']} {item['name']} x{item['quantity']} to order!")
# ๐ Display order summary
def display_order(self, order: Order) -> None:
print(f"\n๐ Order {order['order_id']} Summary:")
print(f"๐ค Customer: {order['customer_name']}")
print(f"๐ Ship to: {order['shipping_address']['city']}")
print("๐ฆ Items:")
for item in order["items"]:
subtotal = item["price"] * item["quantity"]
print(f" {item['emoji']} {item['name']} x{item['quantity']} = ${subtotal:.2f}")
print(f"๐ฐ Total: ${order['total']:.2f}")
print(f"๐ Status: {order['status']}")
# ๐ฎ Let's use it!
processor = OrderProcessor()
# Create shipping address
address: Address = {
"street": "123 Python Lane",
"city": "Codeville",
"zip_code": "12345",
"country": "Pythonia"
}
# Create order
order = processor.create_order("Alice Developer", address)
# Add items
processor.add_item(order, {
"product_id": "BOOK-001",
"name": "Python Mastery",
"quantity": 1,
"price": 29.99,
"emoji": "๐"
})
processor.add_item(order, {
"product_id": "MUG-001",
"name": "Code & Coffee Mug",
"quantity": 2,
"price": 12.99,
"emoji": "โ"
})
processor.display_order(order)
๐ฏ Try it yourself: Add a method to update order status and calculate shipping costs!
๐ฎ Example 2: Game Character System
Letโs make it fun:
# ๐ RPG character system
from typing import TypedDict, List, Dict, Optional
class Stats(TypedDict):
health: int
mana: int
strength: int
defense: int
speed: int
class Skill(TypedDict):
name: str
damage: int
mana_cost: int
emoji: str
description: str
class Equipment(TypedDict):
name: str
slot: Literal["weapon", "armor", "accessory"]
bonus_stats: Dict[str, int]
emoji: str
class Character(TypedDict):
name: str
class_type: Literal["warrior", "mage", "rogue"]
level: int
stats: Stats
skills: List[Skill]
equipment: Dict[str, Optional[Equipment]]
experience: int
class GameEngine:
def __init__(self):
self.characters: Dict[str, Character] = {}
# ๐ฎ Create new character
def create_character(self, name: str, class_type: Literal["warrior", "mage", "rogue"]) -> Character:
base_stats = {
"warrior": Stats(health=100, mana=20, strength=15, defense=12, speed=8),
"mage": Stats(health=60, mana=100, strength=5, defense=8, speed=10),
"rogue": Stats(health=75, mana=50, strength=10, defense=9, speed=15)
}
starter_skills = {
"warrior": [
Skill(name="Sword Slash", damage=20, mana_cost=5,
emoji="โ๏ธ", description="A powerful sword attack"),
],
"mage": [
Skill(name="Fireball", damage=30, mana_cost=15,
emoji="๐ฅ", description="Launches a blazing fireball"),
],
"rogue": [
Skill(name="Sneak Attack", damage=25, mana_cost=10,
emoji="๐ก๏ธ", description="Strike from the shadows"),
]
}
character: Character = {
"name": name,
"class_type": class_type,
"level": 1,
"stats": base_stats[class_type],
"skills": starter_skills[class_type],
"equipment": {
"weapon": None,
"armor": None,
"accessory": None
},
"experience": 0
}
self.characters[name] = character
print(f"๐ Created {class_type} named {name}!")
return character
# ๐ก๏ธ Equip item
def equip_item(self, character: Character, item: Equipment) -> None:
slot = item["slot"]
old_item = character["equipment"][slot]
if old_item:
print(f"๐ Replacing {old_item['emoji']} {old_item['name']}")
character["equipment"][slot] = item
# Apply stat bonuses
for stat, bonus in item["bonus_stats"].items():
if stat in character["stats"]:
character["stats"][stat] += bonus
print(f"โจ Equipped {item['emoji']} {item['name']}!")
# ๐ Display character
def display_character(self, character: Character) -> None:
print(f"\n๐ฎ Character: {character['name']}")
print(f"โ๏ธ Class: {character['class_type']} | Level: {character['level']}")
print("๐ Stats:")
for stat, value in character["stats"].items():
print(f" {stat.capitalize()}: {value}")
print("๐ฏ Skills:")
for skill in character["skills"]:
print(f" {skill['emoji']} {skill['name']} - {skill['damage']} damage")
# ๐ฎ Test the game!
game = GameEngine()
hero = game.create_character("Pythonista", "mage")
# Equip some items
magic_staff: Equipment = {
"name": "Staff of Wisdom",
"slot": "weapon",
"bonus_stats": {"mana": 20, "strength": 5},
"emoji": "๐ฎ"
}
game.equip_item(hero, magic_staff)
game.display_character(hero)
๐ Advanced Concepts
๐งโโ๏ธ Advanced Topic 1: Inheritance and Composition
When youโre ready to level up, try this advanced pattern:
# ๐ฏ Advanced TypedDict patterns
from typing import TypedDict, Union, Literal
# Base types
class BaseEntity(TypedDict):
id: str
created_at: str
updated_at: str
class Timestamps(TypedDict):
created_at: str
updated_at: str
# Composition approach
class UserBase(TypedDict):
name: str
email: str
class AdminUser(UserBase, Timestamps):
role: Literal["admin"]
permissions: List[str]
class RegularUser(UserBase, Timestamps):
role: Literal["user"]
subscription: Literal["free", "premium"]
# Union type for all users
User = Union[AdminUser, RegularUser]
# ๐ช Type narrowing function
def process_user(user: User) -> None:
print(f"Processing {user['name']} ({user['role']})")
if user["role"] == "admin":
# TypeScript knows this is AdminUser now!
print(f" ๐ก๏ธ Permissions: {', '.join(user['permissions'])}")
else:
# And this is RegularUser!
print(f" ๐ Subscription: {user['subscription']}")
๐๏ธ Advanced Topic 2: Runtime Validation
For the brave developers:
# ๐ Runtime validation with TypedDict
from typing import TypedDict, get_type_hints, get_origin, get_args
import json
class Config(TypedDict):
debug: bool
port: int
host: str
features: List[str]
def validate_typed_dict(data: dict, typed_dict_class: type) -> bool:
"""Validate dictionary against TypedDict schema"""
hints = get_type_hints(typed_dict_class)
for field, expected_type in hints.items():
if field not in data:
print(f"โ Missing required field: {field}")
return False
value = data[field]
# Simple type checking (can be extended)
if expected_type == str and not isinstance(value, str):
print(f"โ Field '{field}' should be string, got {type(value).__name__}")
return False
elif expected_type == int and not isinstance(value, int):
print(f"โ Field '{field}' should be int, got {type(value).__name__}")
return False
elif expected_type == bool and not isinstance(value, bool):
print(f"โ Field '{field}' should be bool, got {type(value).__name__}")
return False
print("โ
Validation passed!")
return True
# Test validation
config_data = {
"debug": True,
"port": 8000,
"host": "localhost",
"features": ["api", "websocket"]
}
validate_typed_dict(config_data, Config)
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Mutability Confusion
# โ Wrong way - modifying TypedDict class attributes!
class Settings(TypedDict):
theme: str
Settings.theme = "dark" # ๐ฅ This doesn't do what you think!
# โ
Correct way - create instances!
settings: Settings = {
"theme": "dark"
}
๐คฏ Pitfall 2: Total vs Partial
# โ Dangerous - all fields required by default!
class UserProfile(TypedDict):
name: str
bio: str # What if user hasn't written bio yet?
avatar: str # What if no avatar uploaded?
# โ
Safe - use total=False for optional fields!
class UserProfile(TypedDict):
name: str # Always required
class UserProfileOptional(TypedDict, total=False):
bio: str # Optional
avatar: str # Optional
class FullUserProfile(UserProfile, UserProfileOptional):
pass # Name is required, bio and avatar are optional!
๐ค Pitfall 3: Runtime Type Checking
# โ Wrong assumption - TypedDict doesn't validate at runtime!
def process_user(user: User):
# This won't catch type errors at runtime!
print(user["name"])
# โ
Add runtime validation if needed!
def process_user_safe(user: User):
if "name" not in user:
raise ValueError("โ ๏ธ User must have a name!")
if not isinstance(user["name"], str):
raise TypeError("โ ๏ธ Name must be a string!")
print(user["name"])
๐ ๏ธ Best Practices
- ๐ฏ Be Specific: Define exact types, avoid
Any
- ๐ Use Descriptive Names:
UserProfile
notUP
- ๐ก๏ธ Separate Required/Optional: Use inheritance for clarity
- ๐จ Group Related Types: Keep TypedDicts organized
- โจ Add Validation: Consider runtime checks for external data
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Task Management System
Create a type-safe task management application:
๐ Requirements:
- โ Tasks with title, description, status, and priority
- ๐ท๏ธ Categories and tags for organization
- ๐ค User assignment with roles
- ๐ Due dates and reminders
- ๐จ Each task needs an emoji based on category!
๐ Bonus Points:
- Add subtasks support
- Implement task dependencies
- Create a progress tracking system
๐ก Solution
๐ Click to see solution
# ๐ฏ Our type-safe task management system!
from typing import TypedDict, List, Optional, Literal, Dict
from datetime import datetime
class User(TypedDict):
id: str
name: str
role: Literal["admin", "member", "viewer"]
emoji: str
class Subtask(TypedDict):
id: str
title: str
completed: bool
class Task(TypedDict):
id: str
title: str
description: str
status: Literal["todo", "in_progress", "review", "done"]
priority: Literal["low", "medium", "high", "critical"]
category: Literal["feature", "bug", "docs", "refactor"]
tags: List[str]
assignee: Optional[User]
due_date: Optional[str]
subtasks: List[Subtask]
dependencies: List[str] # Task IDs
emoji: str
class TaskManager:
def __init__(self):
self.tasks: Dict[str, Task] = {}
self.task_counter = 0
# Category emoji mapping
self.category_emojis = {
"feature": "โจ",
"bug": "๐",
"docs": "๐",
"refactor": "๐ง"
}
# โ Create new task
def create_task(self,
title: str,
description: str,
category: Literal["feature", "bug", "docs", "refactor"],
priority: Literal["low", "medium", "high", "critical"] = "medium") -> Task:
self.task_counter += 1
task_id = f"TASK-{self.task_counter:04d}"
task: Task = {
"id": task_id,
"title": title,
"description": description,
"status": "todo",
"priority": priority,
"category": category,
"tags": [],
"assignee": None,
"due_date": None,
"subtasks": [],
"dependencies": [],
"emoji": self.category_emojis[category]
}
self.tasks[task_id] = task
print(f"โ
Created task: {task['emoji']} {title} [{task_id}]")
return task
# ๐ค Assign task to user
def assign_task(self, task_id: str, user: User) -> None:
if task_id in self.tasks:
self.tasks[task_id]["assignee"] = user
print(f"๐ Assigned {self.tasks[task_id]['title']} to {user['emoji']} {user['name']}")
# ๐ Add subtask
def add_subtask(self, task_id: str, subtask_title: str) -> None:
if task_id in self.tasks:
subtask: Subtask = {
"id": f"{task_id}-SUB-{len(self.tasks[task_id]['subtasks']) + 1}",
"title": subtask_title,
"completed": False
}
self.tasks[task_id]["subtasks"].append(subtask)
print(f"๐ Added subtask: {subtask_title}")
# ๐ฏ Get completion percentage
def get_task_progress(self, task_id: str) -> float:
if task_id not in self.tasks:
return 0.0
task = self.tasks[task_id]
if task["status"] == "done":
return 100.0
if not task["subtasks"]:
status_progress = {
"todo": 0,
"in_progress": 50,
"review": 75,
"done": 100
}
return status_progress[task["status"]]
completed = sum(1 for st in task["subtasks"] if st["completed"])
return (completed / len(task["subtasks"])) * 100
# ๐ Display task summary
def display_task(self, task_id: str) -> None:
if task_id not in self.tasks:
print("โ Task not found!")
return
task = self.tasks[task_id]
progress = self.get_task_progress(task_id)
print(f"\n๐ Task: {task['emoji']} {task['title']} [{task_id}]")
print(f"๐ Description: {task['description']}")
print(f"๐ Status: {task['status']} | Priority: {task['priority']}")
print(f"๐ท๏ธ Category: {task['category']}")
if task["assignee"]:
print(f"๐ค Assignee: {task['assignee']['emoji']} {task['assignee']['name']}")
if task["subtasks"]:
print(f"๐ Subtasks ({len([st for st in task['subtasks'] if st['completed']])}/{len(task['subtasks'])}):")
for st in task["subtasks"]:
check = "โ
" if st["completed"] else "โฌ"
print(f" {check} {st['title']}")
print(f"๐ Progress: {progress:.0f}%")
# ๐ฎ Test it out!
manager = TaskManager()
# Create users
alice: User = {"id": "USR001", "name": "Alice", "role": "admin", "emoji": "๐ฉโ๐ป"}
bob: User = {"id": "USR002", "name": "Bob", "role": "member", "emoji": "๐จโ๐ป"}
# Create tasks
task1 = manager.create_task(
"Implement TypedDict validation",
"Add runtime validation for TypedDict schemas",
"feature",
"high"
)
manager.assign_task(task1["id"], alice)
manager.add_subtask(task1["id"], "Research validation libraries")
manager.add_subtask(task1["id"], "Implement validator function")
manager.add_subtask(task1["id"], "Write unit tests")
manager.display_task(task1["id"])
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create TypedDict schemas with confidence ๐ช
- โ Structure complex data with type safety ๐ก๏ธ
- โ Use inheritance and composition for flexible types ๐ฏ
- โ Implement runtime validation when needed ๐
- โ Build type-safe applications with Python! ๐
Remember: TypedDict brings the best of static typing to Pythonโs dynamic world! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered TypedDict!
Hereโs what to do next:
- ๐ป Practice with the exercises above
- ๐๏ธ Add TypedDict to your existing projects
- ๐ Explore related topics: Protocol, NamedTuple, dataclasses
- ๐ Share your type-safe Python journey with others!
Remember: Every Python expert started as a beginner. Keep coding, keep learning, and most importantly, have fun! ๐
Happy coding! ๐๐โจ