Lesson 21: Lists of Dictionaries#
A collection of student records (list of dictionaries)#
INCORRECT: Combining multiple students into one dictionary#
This code will fail#
This function ensures every student has all required fields
with the correct data types. It also provides a default value
for the major field in case it is not specified.
Parameters:
name (str): The student's full name
age (int): The student's age in years
gpa (float): The student's grade point average (0.0 -4.0)
major (str): The student's declared major (default: "Undecided")
Returns:
dict: A dictionary representing the student with all required fields
"""
return {
"name": name,
"age": age,
"gpa": gpa,
"major": major
}
Create students using the factory function#
All students now have the exact same structure#
Raises:
TypeError: If parameters have incorrect types
ValueError: If parameter values are invalid
"""
# Validate types
if not isinstance(name, str):
raise TypeError("Student name must be a string")
if not isinstance(age, int):
raise TypeError("Student age must be an integer")
if not isinstance(gpa, (int, float)):
raise TypeError("Student GPA must be a number")
if not isinstance(major, str):
raise TypeError("Student major must be a string")
# Validate values
if age < 0 or age > 120:
raise ValueError("Student age must be between 0 and 120")
if gpa < 0.0 or gpa > 4.0:
raise ValueError("Student GPA must be between 0.0 and 4.0")
if not name.strip():
raise ValueError("Student name cannot be empty")
return {
"name": name,
"age": age,
"gpa": gpa,
"major": major
}
Try to create an invalid student#
Add students one at a time#
Collect student data from user input#
age = int(input("Enter student age: "))
major = input("Enter student major: ")
gpa = float(input("Enter student GPA: "))
# Create and add the student record
student = {"name": name, "age": age, "major": major, "gpa": gpa}
students.append(student)
Build the list using the factory function#
Or create the list directly if you have all the data#
Access the first student’s name#
Access the last student’s GPA (using negative indexing)#
Access and compute with the data#
Create a formatted string using multiple accesses#
Compare two students#
Iterate through all students#
print(f"{student['name']}: {status}")
Output:#
Alice Johnson: Dean’s List#
Bob Martinez: Good Standing#
Charlie Davis: Dean’s List#
Output:#
Student #1: Alice Johnson#
Student #2: Bob Martinez#
Student #3: Charlie Davis#
Or use a more Pythonic list comprehension#
Add a new student#
print(f”Total students: {len(students)}“) # Output: Total students: 3
Parameters:
students (list): The list of student dictionaries
name (str): Student's name
age (int): Student's age
major (str): Student's major
gpa (float): Student's GPA
Returns:
dict: The newly created student dictionary
"""
new_student = create_student(name, age, major, gpa)
students.append(new_student)
print(f"Added student: {name}")
return new_student
Use the function#
Insert a student at a specific position (index 2, which will be the third position)#
Parameters:
students (list): List of student dictionaries to display
"""
if not students:
print("No students to display")
return
# Print header
print("\n" + "=" * 70)
print("STUDENT ROSTER")
print("=" * 70)
print(f"{'Name':<25} {'Age':<5} {'Major':<20} {'GPA':<5}")
print("-" * 70)
# Print each student
for student in students:
print(f"{student['name']:<25} {student['age']:<5} {student['major']:<20}
{student[‘gpa’]:<5}“)
print("=" * 70)
print(f"Total students: {len(students)}")
print()
Test the function#
]
Parameters:
students (list): List of student dictionaries
name (str): Name to search for (case -insensitive)
Returns:
dict or None: The student dictionary if found, None otherwise
"""
# Convert search name to lowercase for case -insensitive comparison
name_lower = name.lower()
for student in students:
if student['name'].lower() == name_lower:
return student
# If we get here, the student was not found
return None
Test the function#
Find a student#
Try to find a non -existent student#
print("Student 'David Lee' not found")
Output: Student ‘David Lee’ not found#
This assumes students have an 'id' field in their dictionary.
"""
for student in students:
if student.get('id') == student_id:
return student
return None
Returns:
list: A list of student dictionaries matching the major
"""
matching_students = []
for student in students:
if student['major'].lower() == major.lower():
matching_students.append(student)
return matching_students
Test finding by major#
Parameters:
students (list): List of student dictionaries
name (str): Name of the student to update
new_gpa (float): New GPA value
Returns:
bool: True if student was found and updated, False otherwise
"""
# Find the student
for student in students:
if student['name'].lower() == name.lower():
# Store old value for reporting
old_gpa = student['gpa']
# Update the GPA
student['gpa'] = new_gpa
print(f"Updated {name}'s GPA from {old_gpa} to {new_gpa}")
return True
# Student not found
print(f"Student '{name}' not found")
return False
Test the function#
Update Alice’s GPA#
Verify the change#
Try to update a non -existent student#
Parameters:
students (list): List of student dictionaries
name (str): Name of the student to update
**updates: Keyword arguments representing fields to update
Returns:
bool: True if student was found and updated, False otherwise
Example:
update_student_info(students, "Alice", age=21, major="Data Science")
"""
# Find the student
for student in students:
if student['name'].lower() == name.lower():
# Update each field provided in the updates
for field, value in updates.items():
if field in student:
student[field] = value
else:
print(f"Warning: Student does not have field '{field}', skipping")
print(f"Updated {name}'s information")
return True
# Student not found
print(f"Student '{name}' not found")
return False
Test updating multiple fields#
Update multiple fields at once#
Verify the changes#
Try to update a field that doesn’t exist#
update_student_info(students, “Bob Martinez”, age=23, email=“bob@email.com”)
Output:#
Warning: Student does not have field ‘email’, skipping#
Updated Bob Martinez’s information#
print(f"Student '{name}' not found")
return False
Parameters:
students (list): List of student dictionaries
name (str): Name of the student to remove
Returns:
bool: True if student was found and removed, False otherwise
"""
# Iterate with enumerate to get both index and value
for index, student in enumerate(students):
if student['name'].lower() == name.lower():
# Remove the student using pop() with the index
removed_student = students.pop(index)
print(f"Removed student: {removed_student['name']}")
return True
# Student not found
print(f"Student '{name}' not found")
return False
Test the function#
print(f”Initial student count: {len(students)}“) # Output: Initial student count: 4
Delete a student#
print(f”Student count after deletion: {len(students)}“) # Output: Student count after deletion: 3
Verify Bob is gone#
Parameters:
students (list): List of student dictionaries (modified in place)
field (str): The field name to check
value: The value to match for deletion
Returns:
int: Number of students removed
"""
initial_count = len(students)
# Create a new list excluding students that match the criteria
# The [:] slice assignment replaces the list contents in place
students[:] = [s for s in students if s.get(field) != value]
removed_count = initial_count - len(students)
print(f"Removed {removed_count} student(s)")
return removed_count
Test the function#
print(f”Initial count: {len(students)}“) # Output: Initial count: 4
Delete all Computer Science students#
print(f”Remaining count: {len(students)}“) # Output: Remaining count: 2
{"name": "Diana Lee", "age": 19, "major": "Physics", "gpa": 3.7},
{"name": "Eve Taylor", "age": 20, "major": "Mathematics", "gpa": 3.5}
]
Find all students with GPA above 3.7#
Find all students aged 20 or younger#
Find students in specific majors#
Find students who are either in Math or Physics#
More concise version using ‘in’#
Complex condition: young students with high GPA#
Get name -GPA pairs for CS students#
Create a dictionary mapping names to GPAs for all students#
Parameters:
students (list): List of student dictionaries to search
name (str, optional): Search for name containing this string (case -insensitive)
major (str, optional): Filter by exact major (case -insensitive)
min_gpa (float, optional): Filter for GPA >= this value
max_age (int, optional): Filter for age <= this value
Returns:
list: List of student dictionaries matching all specified criteria
Examples:
search_students(students, major="Computer Science")
search_students(students, min_gpa=3.7, max_age=20)
search_students(students, name="John")
"""
results = students # Start with all students
# Apply each filter if the parameter was provided
if name is not None:
name_lower = name.lower()
results = [s for s in results if name_lower in s['name'].lower()]
if major is not None:
major_lower = major.lower()
results = [s for s in results if s['major'].lower() == major_lower]
if min_gpa is not None:
results = [s for s in results if s['gpa'] >= min_gpa]
if max_age is not None:
results = [s for s in results if s['age'] <= max_age]
return results
Test the search function#
Search for Computer Science students#
Search for students with GPA >= 3.7#
Search with multiple criteria#
Search by partial name match#
Eve Johnson#
Sort by name (alphabetically)#
print(f" {student['name']}: {student['age']} years old")
Output:#
Sorted by age (youngest first):#
Diana Lee: 19 years old#
Alice Johnson: 20 years old#
Charlie Davis: 21 years old#
Bob Martinez: 22 years old#
Sort by GPA (lowest first)#
print(f" {student['name']}: {student['gpa']}")
Output:#
Sorted by GPA (highest first):#
Charlie Davis: 3.9#
Alice Johnson: 3.8#
Diana Lee: 3.7#
Bob Martinez: 3.6#
Sort by age (oldest first)#
{"name": "Alice Johnson", "major": "Computer Science", "gpa": 3.8},
{"name": "Bob Martinez", "major": "Computer Science", "gpa": 3.9},
{"name": "Diana Lee", "major": "Physics", "gpa": 3.7},
{"name": "Eve Taylor", "major": "Mathematics", "gpa": 3.6}
]
Sort by major, then by GPA within each major#
sorted() creates a NEW list, leaving the original unchanged#
.sort() modifies the list IN PLACE, returning None#
{"name": "Eve Taylor", "major": "Mathematics", "gpa": 3.5},
{"name": "Frank Wilson", "major": "Computer Science", "gpa": 3.7}
]
Group students by major#
Display the grouped data#
Eve Taylor (GPA: 3.5)
Physics:#
Diana Lee (GPA: 3.7)
{"name": "Frank Wilson", "major": "Computer Science", "year": 2, "gpa": 3.7}
]
Group by major, then by year within each major#
Display the nested grouping#
Charlie Davis
Mathematics:#
Year 2:#
Eve Taylor
Year 3:#
Bob Martinez
Physics:#
Year 2:#
Diana Lee
Create an index by student ID for instant lookups#
students_by_id = {student[‘id’]: student for student in students}
Now lookups are instant (O(1) instead of O(n))#
Create an index by email#
Count total students#
Calculate average GPA#
Calculate average age#
Find minimum and maximum GPA#
Find the student with the highest GPA#
Calculate total credits across all students#
students = [
{"name": "Alice Johnson", "major": "Computer Science", "gpa": 3.8},
{"name": "Bob Martinez", "major": "Mathematics", "gpa": 3.6},
{"name": "Charlie Davis", "major": "Computer Science", "gpa": 3.9},
{"name": "Diana Lee", "major": "Physics", "gpa": 3.7},
{"name": "Eve Taylor", "major": "Mathematics", "gpa": 3.5},
{"name": "Frank Wilson", "major": "Computer Science", "gpa": 3.7}
]
Calculate average GPA by major#
Count students by major#
Count students by year#
Find majors that increased in popularity#
Equivalent JSON (as a string)#
"age": 20,
"major": "Computer Science",
"gpa": 3.8,
"courses": ["Data Structures", "Algorithms", "Databases"]
}
Convert to JSON string#
Convert to pretty -printed JSON (with indentation)#
“Databases”#
]#
}#
Convert to Python dictionary#
You can now work with it like any Python dictionary#
student[‘gpa’] = 3.7
Write to JSON file#
The file now contains properly formatted JSON#
# Display the loaded data
for student in loaded_students:
print(f" - {student['name']}: {student['major']}, GPA {student['gpa']}")
Output:#
Loaded 3 students from file#
Alice Johnson: Computer Science, GPA 3.8
Bob Martinez: Mathematics, GPA 3.6
Charlie Davis: Physics, GPA 3.9
Invalid JSON examples#
Returns:
The parsed JSON data, or None if an error occurred
"""
try:
with open(filename, "r") as file:
return json.load(file)
except FileNotFoundError:
print(f"Error: File '{filename}' not found")
return None
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in '{filename}'")
print(f" {e}")
return None
except Exception as e:
print(f"Unexpected error reading '{filename}': {e}")
return None
Use the safe function#
"name": "Tech University",
"established": 1990,
"departments": [
{
"name": "Computer Science",
"head": "Dr. Smith",
"students": [
{"name": "Alice Johnson", "year": 2, "gpa": 3.8},
{"name": "Bob Martinez", "year": 3, "gpa": 3.6}
],
"courses": ["Intro to Programming", "Data Structures", "Algorithms"]
},
{
"name": "Mathematics",
"head": "Dr. Johnson",
"students": [
{"name": "Charlie Davis", "year": 2, "gpa": 3.9},
{"name": "Diana Lee", "year": 1, "gpa": 3.7}
],
"courses": ["Calculus", "Linear Algebra", "Statistics"]
}
]
Access first department name (one level deep)#
Access department head (two levels deep)#
Access first student in first department (three levels deep)#
Access a specific student’s GPA (four levels deep)#
Access third course in second department#
The outer loop iterates through departments, and the inner loop iterates through students within each department. This pattern of nested loops matches the nested structure of the data.
You can extract specific information while iterating:
Safe access using .get()#
Example:
safe_nested_get(university, 'departments', 0, 'students', 1, 'name')
Parameters:
data: The data structure to navigate
*keys: Series of keys/indexes to follow
default: Value to return if path doesn't exist
Returns:
The value at the specified path, or default if path doesn't exist
"""
result = data
for key in keys:
try:
result = result[key]
except (KeyError, IndexError, TypeError):
return default
return result
Test safe access#
Access non -existent path#
Access with wrong type#
Each student record will include department information.
Parameters:
university_data: Nested university dictionary
Returns:
list: Flat list of student dictionaries with department info
"""
all_students = []
for department in university_data.get('departments', []):
dept_name = department.get('name', 'Unknown')
dept_head = department.get('head', 'Unknown')
for student in department.get('students', []):
# Create a flattened record combining student and department data
flat_student = {
'student_name': student.get('name', 'Unknown'),
'year': student.get('year', 0),
'gpa': student.get('gpa', 0.0),
'department': dept_name,
'department_head': dept_head,
'university': university_data.get('name', 'Unknown')
}
all_students.append(flat_student)
return all_students
Flatten the data#
Flattened student data:#
Alice Johnson - Computer Science - Year 2 - GPA 3.8#
Bob Martinez - Computer Science - Year 3 - GPA 3.6#
Charlie Davis - Mathematics - Year 2 - GPA 3.9#
Diana Lee - Mathematics - Year 1 - GPA 3.7#
Parameters:
flat_students: List of flat student dictionaries
Returns:
dict: Dictionary mapping department names to lists of students
"""
from collections import defaultdict
grouped = defaultdict(list)
for student in flat_students:
department = student.get('department', 'Unknown')
# Create a simplified student record without redundant department info
simple_student = {
'name': student['student_name'],
'year': student['year'],
'gpa': student['gpa']
}
grouped[department].append(simple_student)
return dict(grouped)
Restructure the flattened data#
Mathematics:#
Charlie Davis - Year 2#
Diana Lee - Year 1#
Helps you understand complex JSON/dictionary structures.
Parameters:
data: The data structure to visualize
indent: Current indentation level (used internally)
max_depth: Maximum depth to print (prevents infinite recursion)
"""
if indent >= max_depth:
print(" " * indent + "... (max depth reached)")
return
spaces = " " * indent
if isinstance(data, dict):
for key, value in data.items():
if isinstance(value, (dict, list)):
print(f"{spaces}{key}:")
print_structure(value, indent + 1, max_depth)
else:
print(f"{spaces}{key}: {type(value).__name__} = {value}")
elif isinstance(data, list):
if len(data) == 0:
print(f"{spaces}(empty list)")
else:
print(f"{spaces}[{len(data)} items]")
# Show first item as example
if len(data) > 0:
print(f"{spaces}Example item [0]:")
print_structure(data[0], indent + 1, max_depth)
else:
print(f"{spaces}{type(data).__name__} = {data}")
Visualize the university structure#
}
Invalid students (various problems)#
Parameters:
student: Dictionary to validate
Returns:
tuple: (is_valid, error_message)
is_valid is True if valid, False otherwise
error_message describes the problem, or "Valid" if no problems
"""
# Define required fields and their types
required_fields = {
"name": str,
"age": int,
"major": str,
"gpa": (int, float) # Allow both int and float for GPA
}
# Check that all required fields exist
for field, expected_type in required_fields.items():
if field not in student:
return False, f"Missing required field: '{field}'"
# Check type
if not isinstance(student[field], expected_type):
actual_type = type(student[field]).__name__
if isinstance(expected_type, tuple):
type_names = " or ".join(t.__name__ for t in expected_type)
expected_name = type_names
else:
expected_name = expected_type.__name__
return False, f"Field '{field}' has wrong type. Expected {expected_name}, got
{actual_type}”
# Validate name is not empty
if not student["name"].strip():
return False, "Name cannot be empty"
# Validate age is in reasonable range
if student["age"] < 16 or student["age"] > 100:
return False, f"Age must be between 16 and 100, got {student['age']}"
# Validate major is not empty
if not student["major"].strip():
return False, "Major cannot be empty"
# Validate GPA is in valid range
if student["gpa"] < 0.0 or student["gpa"] > 4.0:
return False, f"GPA must be between 0.0 and 4.0, got {student['gpa']}"
# If we get here, the student is valid
return True, "Valid"
Test validation with various inputs#
Parameters:
students: List of student dictionaries
Returns:
tuple: (all_valid, validation_report)
all_valid is True if all students are valid
validation_report is a list of (index, is_valid, message) tuples
"""
report = []
all_valid = True
for index, student in enumerate(students):
is_valid, message = validate_student(student)
report.append((index, is_valid, message))
if not is_valid:
all_valid = False
return all_valid, report
Test collection validation#
all_valid, report = validate_student_collection(students)
Attempts to fix common data quality issues:
- Convert string numbers to actual numbers
- Trim whitespace from strings
- Normalize text casing
Parameters:
raw_student: Dictionary that might need cleaning
Returns:
tuple: (cleaned_student, warnings)
cleaned_student is the cleaned dictionary
warnings is a list of issues that were fixed
"""
cleaned = {}
warnings = []
# Clean name
if "name" in raw_student:
cleaned["name"] = raw_student["name"].strip()
if raw_student["name"] != cleaned["name"]:
warnings.append("Trimmed whitespace from name")
# Clean and convert age
if "age" in raw_student:
if isinstance(raw_student["age"], str):
try:
cleaned["age"] = int(raw_student["age"])
warnings.append("Converted age from string to integer")
except ValueError:
cleaned["age"] = raw_student["age"] # Keep as -is, will fail validation
warnings.append(f"Could not convert age '{raw_student['age']}' to integer")
else:
cleaned["age"] = raw_student["age"]
# Clean major
if "major" in raw_student:
cleaned["major"] = raw_student["major"].strip()
if raw_student["major"] != cleaned["major"]:
warnings.append("Trimmed whitespace from major")
# Clean and convert GPA
if "gpa" in raw_student:
if isinstance(raw_student["gpa"], str):
try:
cleaned["gpa"] = float(raw_student["gpa"])
warnings.append("Converted GPA from string to float")
except ValueError:
cleaned["gpa"] = raw_student["gpa"] # Keep as -is, will fail validation
warnings.append(f"Could not convert GPA '{raw_student['gpa']}' to float")
else:
cleaned["gpa"] = raw_student["gpa"]
return cleaned, warnings
Test data cleaning#
"""
Import a student record with cleaning and validation.
Returns:
tuple: (student, success, messages)
student is the cleaned dictionary (or None if invalid)
success is True if import succeeded
messages is a list of informational/error messages
"""
messages = []
# Step 1: Clean the data
cleaned, clean_warnings = clean_student_data(raw_student)
messages.extend(clean_warnings)
# Step 2: Validate the cleaned data
is_valid, validation_msg = validate_student(cleaned)
messages.append(f"Validation: {validation_msg}")
if is_valid:
return cleaned, True, messages
else:
return None, False, messages
Test safe import#
test_records = [
{"name": " Alice ", "age": "20", "major": "CS", "gpa": "3.8"},
{"name": "Bob", "age": "invalid", "major": "Math", "gpa": "3.6"},
{"name": "", "age": "20", "major": "Physics", "gpa": "3.7"},
]
if success:
successfully_imported.append(student)
print(f"✓ Successfully imported: {student['name']}")
else:
print(f"✗ Failed to import: {raw}")
if messages:
for msg in messages:
print(f" {msg}")
print()
Using the system#
Nothing prevents invalid modifications#
Could it be a student? An employee? Something else?#
You have to examine the keys to figure it out#
def __init__(self, name, gpa):
"""Initialize a new student"""
self.name = name
self.gpa = gpa
self.courses = []
def add_course(self, course_name):
"""Add a course to the student's schedule"""
self.courses.append(course_name)
def calculate_status(self):
"""Determine academic status"""
if self.gpa >= 3.5:
return "Dean's List"
return "Good Standing"
def update_gpa(self, new_gpa):
"""Update GPA with validation"""
if 0.0 <= new_gpa <= 4.0:
self.gpa = new_gpa
else:
raise ValueError("GPA must be between 0.0 and 4.0")
def display(self):
"""Display student information"""
print(f"{self.name}: {self.calculate_status()}")
Using the object -oriented version#
Attempting invalid modification raises an error#
print(student_dict[“name”]) # Access using brackets and string key
Object approach (preview)#
update_gpa(student_dict, 3.9)
Object approach: method belongs to the object#
Object approach#
Both can be processed similarly#
Object approach: constructor (init method)#
Simpler Mental Model: Dictionaries are simpler to understand initially. You’re working with familiar built -in data types and straightforward functions. This allows you to focus on program logic without the additional complexity of class syntax and object -oriented concepts.
Still Useful: Dictionaries remain extremely useful even when you know OOP. They’re perfect for configuration data, JSON/API responses, temporary data transformations, and situations where defining a full class would be overkill.
Natural Progression: Learning dictionaries first makes learning OOP easier because you already understand the problems that OOP solves. You’ve experienced the limitations firsthand, so you’ll appreciate why objects organize code better.
Foundation for Understanding: Many object -oriented concepts map directly to patterns you’ve already learned: • Schema validation → Type checking and property validation • Factory functions → Constructors • Consistent structure → Class definitions • Related functions → Methods 10.5 Looking Ahead In upcoming chapters on object -oriented programming, you will learn: Classes and Objects: How to define your own data types (classes) and create instances of those types (objects). Encapsulation: How to bundle data and methods together, protecting internal state and exposing a clean interface. Inheritance: How to create specialized types based on existing types, reusing and extending functionality. Polymorphism: How different objects can respond to the same method calls in type - appropriate ways. Design Patterns: How to structure larger programs using objects to create maintainable, scalable code. As you make this transition, remember that the skills you’ve developed with dictionaries transfer directly. You already understand data modeling, validation, CRUD operations, and
data transformations. Now you’ll learn how to express these concepts more elegantly with objects.
This will cause errors#
This can skip elements or cause unexpected behavior#
CORRECT: Iterate over a copy#
This creates three references to the SAME dictionary!#
Or use copy()#
CORRECT: .sort() modifies in place#
with open(‘students.json’) as f:
students = json.load(f)
Immediately using the data without validation#
Why is consistency important when working with lists of dictionaries? What problems arise when dictionaries in the same list have different structures?
Compare and contrast list comprehensions with traditional for loops for filtering data. When would you choose one over the other?
How does the key parameter in sorted() work? Why is a lambda function often used with it?
What are the trade -offs between using sorted() versus .sort()? When would you prefer each?
Why is defaultdict useful for grouping operations? How does it simplify code compared to regular dictionaries?
What is the purpose of JSON in modern programming? Why is it important that JSON is language -independent?
How would you explain the difference between json.dumps()/loads() and json.dump()/load() to someone learning Python?
What strategies can you use to safely access nested data structures without causing KeyError or IndexError exceptions?
Why is data validation critical when working with external data sources? What could go wrong without validation?
How do the patterns you learned with lists of dictionaries prepare you for object - oriented programming? What parallels do you see?
Filtering and Searching List comprehensions provide a concise, readable way to filter data based on conditions. You can combine multiple conditions with and/or, extract specific fields, and build flexible search functions that accept multiple optional criteria. Generator expressions offer memory -efficient alternatives for large datasets.
Sorting and Organization The sorted() function with lambda key functions allows sorting by any field or combination of fields. Multi -level sorting uses tuples to specify priority order. Understanding the difference between sorted() (returns new list) and .sort() (modifies in place) is crucial for correct program behavior.
Grouping and Aggregation defaultdict simplifies grouping operations by automatically creating default values for new keys. Aggregations like sum, average, min, and max compute summary statistics. The pattern of grouping data then computing per -group aggregates is fundamental to data analysis .
JSON: The Universal Data Format JSON is the standard format for data exchange in modern programming. Understanding how to convert between Python objects and JSON (using dumps/loads for strings, dump/load for files) allows your programs to communicate with web APIs, save data portably, and integrate with other systems.
Nested Structures Require Care Real-world data often involves nested structures. Navigate nested data by chaining access operations. Use safe access patterns ( .get() with defaults, try -except blocks, or helper functions) to prevent crashes when data is missing or malformed. Understand when to flatten nested data into simpler structures for analysis.
Validation Ensures Quality Always validate data from external sources before using it. Check for required fields, correct types, and valid value ranges. Combine validation with data cleaning to handle common issues like whitespace or string numbers. Early validation prevents subtle bugs and data corruption.
From Dictionaries to Objects The patterns you learned —organizing data in dictionaries, using factory functions, implementing operations as functions —map directly to object - oriented programming. Dictionary keys become object attributes, functions become methods, and factory functions become constructors. Understanding these parallels will make learning OOP easier.
Choose the Right Tool Dictionaries are perfect for dynamic data, JSON interchange, configuration, and prototyping. Objects are better for complex behavior, encapsulation, and large systems. Understanding both gives you flexibility to choose the most appropriate tool for each s ituation.
End of Chapter 21
@media (prefers -color-scheme: dark) {
:root {
--accent: #7385ff;
--timeline-ln: linear-gradient(to bottom, transparent 0%, transparent 3%, #6264a7
@media (prefers -contrast: more),
(forced-colors: active) {
:root {
--accent: ActiveText;
--timeline-ln: ActiveText;
--timeline-border: Canvas;
--bg-card: Canvas;
--bg-hover: Canvas;
--text-title: CanvasText;
--text-sub: CanvasText;
--shadow: 0 2px 10px Canvas;
--hover-shadow: 0 4px 14px Canvas;
--border: ButtonBorder;
}
}
.insights-container {
display: grid;
grid-template -columns: repeat(2,minmax(240px,1fr));
padding: 0px 16px 0px 16px;
gap: 16px;
margin: 0 0;
font-family: var( --font);
}
.insight-card:last -child:nth -child(odd){
grid-column: 1 / -1;
}
.insight-card {
background -color: var( --bg-card);
border-radius: var( --radius);
border: 1px solid var( --border);
box-shadow: var( --shadow);
min-width: 220px;
padding: 16px 20px 16px 20px;
}
.insight-card:hover {
background -color: var( --bg-hover);
}
.insight-card h4 {
margin: 0px 0px 8px 0px;
font-size: 1.1rem;
color: var( --text-accent);
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.insight-card .icon {
display: inline -flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
font-size: 1.1rem;
color: var( --text-accent);
}
.insight-card p {
font-size: 0.92rem;
color: var( --text-sub);
line-height: 1.5;
margin: 0px;
overflow-wrap: var( --overflow-wrap);
}
.insight-card p b, .insight -card p strong {
font-weight: 600;
}
.metrics-container {
display:grid;
grid-template -columns:repeat(2,minmax(210px,1fr));
font-family: var( --font);
padding: 0px 16px 0px 16px;
gap: 16px;
}
.metric-card:last -child:nth -child(odd){
grid-column:1 / -1;
}
.metric-card {
flex: 1 1 210px;
padding: 16px;
background -color: var( --bg-card);
border-radius: var( --radius);
border: 1px solid var( --border);
text-align: center;
display: flex;
flex-direction: column;
gap: 8px;
}
.metric-card:hover {
background -color: var( --bg-hover);
}
.metric-card h4 {
margin: 0px;
font-size: 1rem;
color: var( --text-title);
font-weight: 600;
}
.metric-card .metric -card-value {
margin: 0px;
font-size: 1.4rem;
font-weight: 600;
color: var( --text-accent);
}
.metric-card p {
font-size: 0.85rem;
color: var( --text-sub);
line-height: 1.45;
margin: 0;
overflow-wrap: var( --overflow-wrap);
}
.timeline -container {
position: relative;
margin: 0 0 0 0;
padding: 0px 16px 0px 56px;
list-style: none;
font-family: var( --font);
font-size: 0.9rem;
color: var( --text-sub);
line-height: 1.4;
}
.timeline -container::before {
content: "";
position: absolute;
top: 0;
left: calc( -40px + 56px);
width: 2px;
height: 100%;
background: var( --timeline-ln);
}
.timeline -container > li {
position: relative;
margin-bottom: 16px;
padding: 16px 20px 16px 20px;
border-radius: var( --radius);
background: var( --bg-card);
border: 1px solid var( --border);
}
.timeline -container > li:last -child {
margin-bottom: 0px;
}
.timeline -container > li:hover {
background -color: var( --bg-hover);
}
.timeline -container > li::before {
content: "";
position: absolute;
top: 18px;
left: -40px;
width: 14px;
height: 14px;
background: var( --accent);
border: var( --timeline-border) 2px solid;
border-radius: 50%;
transform: translateX( -50%);
box-shadow: 0px 0px 2px 0px #00000012, 0px 4px 8px 0px #00000014;
}
.timeline -container > li h4 {
margin: 0 0 5px;
font-size: 1rem;
font-weight: 600;
color: var( --accent);
}
.timeline -container > li h4 em {
margin: 0 0 5px;
font-size: 1rem;
font-weight: 600;
color: var( --accent);
font-style: normal;
}
.timeline -container > li * {
margin: 0;
font-size: 0.9rem;
color: var( --text-sub);
line-height: 1.4;
}
.timeline -container > li * b, .timeline -container > li * strong {
font-weight: 600;
}
@media (max -width:600px){
.metrics-container,
.insights-container{
grid-template -columns:1fr;
}
}
<h4> Code & Formatting Fixes</h4>
<p>Correct HTML -escaped characters in code (e.g. `<`, `>`) and minor typos.
Ensure all Python examples run without errors and display as intended.
<h4> Clarity & Explanation</h4>
<p>Add brief explanations when introducing new or advanced concepts (like lambda
functions, defaultdict, etc.). Enhance or simplify wording in a few places to be more beginner -friendly.
<h4> Structural Tweaks</h4>
<p>Align section content with headings (e.g. cover "map" or adjust title), merge or
reference overlapping sections to avoid redundancy, and highlight important terms or takeaways for emphasis.