Introduction to Python Dictionaries
Table of Contents
1. [What are Dictionaries](#what-are-dictionaries) 2. [Dictionary Characteristics](#dictionary-characteristics) 3. [Creating Dictionaries](#creating-dictionaries) 4. [Accessing Dictionary Elements](#accessing-dictionary-elements) 5. [Dictionary Methods](#dictionary-methods) 6. [Dictionary Operations](#dictionary-operations) 7. [Advanced Dictionary Techniques](#advanced-dictionary-techniques) 8. [Performance Considerations](#performance-considerations) 9. [Common Use Cases](#common-use-cases) 10. [Best Practices](#best-practices)What are Dictionaries
Python dictionaries are one of the most versatile and powerful built-in data structures. A dictionary is an unordered collection of key-value pairs, where each key is unique and maps to a specific value. Dictionaries are mutable, meaning they can be modified after creation, and they provide fast lookups based on keys.
Key Concepts
| Concept | Description | |---------|-------------| | Key | A unique identifier used to access values in the dictionary | | Value | The data associated with a specific key | | Key-Value Pair | The combination of a key and its corresponding value | | Hash Table | The underlying data structure that makes dictionaries efficient |
Dictionary Characteristics
1. Mutability
Dictionaries can be modified after creation. You can add, update, or remove key-value pairs.`python
Creating a mutable dictionary
student = {"name": "Alice", "age": 20} student["grade"] = "A" # Adding a new key-value pair student["age"] = 21 # Updating existing value print(student) # Output: {'name': 'Alice', 'age': 21, 'grade': 'A'}`2. Unordered Nature (Python < 3.7) vs Insertion Order (Python >= 3.7)
Starting from Python 3.7, dictionaries maintain insertion order as part of the language specification.`python
In Python 3.7+, order is preserved
colors = {"red": "#FF0000", "green": "#00FF00", "blue": "#0000FF"} print(list(colors.keys())) # Output: ['red', 'green', 'blue']`3. Key Requirements
Dictionary keys must be immutable and hashable objects.| Valid Key Types | Invalid Key Types | |-----------------|-------------------| | Strings | Lists | | Numbers (int, float) | Dictionaries | | Tuples (if containing immutable elements) | Sets | | Booleans | Tuples containing mutable elements |
`python
Valid keys
valid_dict = { "string_key": "value1", 42: "value2", (1, 2, 3): "value3", True: "value4" }Invalid keys (will raise TypeError)
invalid_dict = {[1, 2, 3]: "value"} # Lists are not hashable
invalid_dict = {{1, 2}: "value"} # Sets are not hashable
`Creating Dictionaries
1. Using Curly Braces
`python
Empty dictionary
empty_dict = {}Dictionary with initial values
person = { "first_name": "John", "last_name": "Doe", "age": 30, "city": "New York" }Mixed data types
mixed_dict = { "name": "Alice", "scores": [85, 92, 78], "is_student": True, "details": {"major": "Computer Science", "year": 3} }`2. Using the dict() Constructor
`python
From keyword arguments
person1 = dict(name="Bob", age=25, city="Boston")From a list of tuples
person2 = dict([("name", "Carol"), ("age", 28), ("city", "Chicago")])From another dictionary (creates a copy)
person3 = dict(person1)Using zip to combine lists
keys = ["name", "age", "city"] values = ["David", 35, "Denver"] person4 = dict(zip(keys, values))`3. Dictionary Comprehension
`python
Basic dictionary comprehension
squares = {x: x2 for x in range(1, 6)} print(squares) # Output: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}With conditional logic
even_squares = {x: x2 for x in range(1, 11) if x % 2 == 0} print(even_squares) # Output: {2: 4, 4: 16, 6: 36, 8: 64, 10: 100}From existing data
words = ["apple", "banana", "cherry"] word_lengths = {word: len(word) for word in words} print(word_lengths) # Output: {'apple': 5, 'banana': 6, 'cherry': 6}`Accessing Dictionary Elements
1. Using Square Brackets
`python
student = {"name": "Alice", "age": 20, "grade": "A"}
Accessing existing keys
print(student["name"]) # Output: Alice print(student["age"]) # Output: 20Accessing non-existent key raises KeyError
print(student["height"]) # KeyError: 'height'
`2. Using the get() Method
`python
student = {"name": "Alice", "age": 20, "grade": "A"}
Safe access with default value
print(student.get("name")) # Output: Alice print(student.get("height")) # Output: None print(student.get("height", 0)) # Output: 0 (custom default)Comparison table
`| Method | Key Exists | Key Doesn't Exist |
|--------|------------|-------------------|
| dict[key] | Returns value | Raises KeyError |
| dict.get(key) | Returns value | Returns None |
| dict.get(key, default) | Returns value | Returns default |
3. Checking Key Existence
`python
student = {"name": "Alice", "age": 20, "grade": "A"}
Using 'in' operator
if "name" in student: print(f"Student's name is {student['name']}")if "height" not in student: print("Height information not available")
Using keys() method
if "age" in student.keys(): print(f"Age: {student['age']}")`Dictionary Methods
Essential Dictionary Methods
| Method | Description | Returns |
|--------|-------------|---------|
| clear() | Removes all items | None |
| copy() | Returns a shallow copy | New dictionary |
| get(key, default) | Returns value for key | Value or default |
| items() | Returns key-value pairs | dict_items object |
| keys() | Returns all keys | dict_keys object |
| values() | Returns all values | dict_values object |
| pop(key, default) | Removes and returns value | Value or default |
| popitem() | Removes last item | Tuple (key, value) |
| setdefault(key, default) | Gets or sets default value | Value |
| update(other) | Updates with another dict | None |
1. Viewing Dictionary Contents
`python
inventory = {
"apples": 50,
"bananas": 30,
"oranges": 25,
"grapes": 40
}
Getting keys
print("Available items:") for item in inventory.keys(): print(f"- {item}")Getting values
print("Quantities:", list(inventory.values()))Getting key-value pairs
print("Inventory details:") for item, quantity in inventory.items(): print(f"{item}: {quantity}")`2. Modifying Dictionaries
`python
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
Adding new entries
scores["Diana"] = 88 scores.update({"Eve": 91, "Frank": 79})Updating existing entries
scores["Alice"] = 87 # Direct assignment scores.update({"Bob": 94}) # Using update methodUsing setdefault
scores.setdefault("Grace", 0) # Adds Grace with score 0 scores.setdefault("Alice", 0) # Alice already exists, no changeprint(scores)
`
3. Removing Dictionary Elements
`python
student_grades = {
"math": 95,
"science": 88,
"english": 92,
"history": 85,
"art": 90
}
Using del statement
del student_grades["art"]Using pop() method
math_grade = student_grades.pop("math") print(f"Math grade was: {math_grade}")Using pop() with default
pe_grade = student_grades.pop("pe", "Not taken") print(f"PE grade: {pe_grade}")Using popitem() - removes last inserted item
subject, grade = student_grades.popitem() print(f"Removed {subject}: {grade}")Clear all items
student_grades.clear()
`Dictionary Operations
1. Iteration Patterns
`python
restaurant_menu = {
"burger": 12.99,
"pizza": 15.50,
"salad": 8.75,
"pasta": 13.25,
"sandwich": 9.50
}
Iterate over keys (default behavior)
print("Menu items:") for item in restaurant_menu: print(f"- {item}")Iterate over values
print("Prices:") for price in restaurant_menu.values(): print(f"${price:.2f}")Iterate over key-value pairs
print("Complete menu:") for item, price in restaurant_menu.items(): print(f"{item}: ${price:.2f}")Iterate with enumerate for indexing
print("Numbered menu:") for index, (item, price) in enumerate(restaurant_menu.items(), 1): print(f"{index}. {item}: ${price:.2f}")`2. Dictionary Merging and Updating
`python
Original dictionaries
defaults = {"theme": "light", "language": "en", "notifications": True} user_prefs = {"theme": "dark", "font_size": 14}Method 1: Using update()
settings = defaults.copy() settings.update(user_prefs) print("Method 1:", settings)Method 2: Using dictionary unpacking (Python 3.5+)
settings = {defaults, user_prefs} print("Method 2:", settings)Method 3: Using union operator (Python 3.9+)
settings = defaults | user_prefs
print("Method 3:", settings)
Conditional merging
def merge_configs(default, custom): result = default.copy() for key, value in custom.items(): if key in result: print(f"Overriding {key}: {result[key]} -> {value}") else: print(f"Adding new setting {key}: {value}") result[key] = value return resultfinal_settings = merge_configs(defaults, user_prefs)
`
3. Nested Dictionaries
`python
Complex nested structure
company = { "name": "Tech Solutions Inc", "founded": 2010, "departments": { "engineering": { "employees": 45, "manager": "Alice Johnson", "teams": { "frontend": ["React", "Vue", "Angular"], "backend": ["Python", "Java", "Node.js"], "mobile": ["iOS", "Android", "React Native"] } }, "marketing": { "employees": 12, "manager": "Bob Smith", "campaigns": ["social_media", "email", "content"] } } }Accessing nested data
print(f"Company: {company['name']}") print(f"Engineering manager: {company['departments']['engineering']['manager']}") print(f"Frontend technologies: {company['departments']['engineering']['teams']['frontend']}")Safe nested access function
def safe_get_nested(dictionary, keys, default=None): """Safely get nested dictionary values""" for key in keys: if isinstance(dictionary, dict) and key in dictionary: dictionary = dictionary[key] else: return default return dictionaryUsing the safe access function
manager = safe_get_nested(company, ["departments", "hr", "manager"], "Not found") print(f"HR manager: {manager}") # Output: HR manager: Not found`Advanced Dictionary Techniques
1. defaultdict from collections
`python
from collections import defaultdict
Regular dictionary approach
word_count = {} text = "hello world hello python world" for word in text.split(): if word in word_count: word_count[word] += 1 else: word_count[word] = 1Using defaultdict
word_count_default = defaultdict(int) for word in text.split(): word_count_default[word] += 1print("Regular dict:", dict(word_count)) print("defaultdict:", dict(word_count_default))
Grouping items with defaultdict
from collections import defaultdictstudents = [ ("Alice", "Math"), ("Bob", "Science"), ("Charlie", "Math"), ("Diana", "Science"), ("Eve", "Math") ]
Group students by subject
subject_groups = defaultdict(list) for student, subject in students: subject_groups[subject].append(student)print("Students by subject:")
for subject, student_list in subject_groups.items():
print(f"{subject}: {student_list}")
`
2. Counter from collections
`python
from collections import Counter
Counting elements
fruits = ["apple", "banana", "apple", "cherry", "banana", "apple"] fruit_counter = Counter(fruits) print("Fruit counts:", fruit_counter)Most common elements
print("Most common:", fruit_counter.most_common(2))Counter arithmetic
counter1 = Counter(["a", "b", "c", "a", "b"]) counter2 = Counter(["a", "b", "b", "d"])print("Union:", counter1 | counter2)
print("Intersection:", counter1 & counter2)
print("Difference:", counter1 - counter2)
print("Addition:", counter1 + counter2)
`
3. OrderedDict from collections
`python
from collections import OrderedDict
Regular dict (order preserved in Python 3.7+)
regular_dict = {"first": 1, "second": 2, "third": 3}OrderedDict with special methods
ordered_dict = OrderedDict([("first", 1), ("second", 2), ("third", 3)])Move to end
ordered_dict.move_to_end("first") print("After move_to_end:", list(ordered_dict.keys()))Pop from beginning or end
last_item = ordered_dict.popitem(last=True) first_item = ordered_dict.popitem(last=False) print(f"Last: {last_item}, First: {first_item}")`Performance Considerations
Time Complexity Analysis
| Operation | Average Case | Worst Case | |-----------|--------------|------------| | Access (get item) | O(1) | O(n) | | Insert | O(1) | O(n) | | Delete | O(1) | O(n) | | Search (in operator) | O(1) | O(n) | | Iteration | O(n) | O(n) |
Memory and Performance Tips
`python
import sys
import time
Memory comparison
list_data = [(i, i2) for i in range(1000)] dict_data = {i: i2 for i in range(1000)}print(f"List size: {sys.getsizeof(list_data)} bytes") print(f"Dict size: {sys.getsizeof(dict_data)} bytes")
Performance comparison
def time_operation(operation, iterations=100000): start = time.time() for _ in range(iterations): operation() return time.time() - startDictionary vs list lookup
large_dict = {i: f"value_{i}" for i in range(10000)} large_list = [(i, f"value_{i}") for i in range(10000)]Fast dictionary lookup
def dict_lookup(): return large_dict.get(5000)Slow list search
def list_search(): for key, value in large_list: if key == 5000: return valueprint(f"Dict lookup time: {time_operation(dict_lookup):.6f} seconds")
print(f"List search time: {time_operation(list_search):.6f} seconds")
`
Common Use Cases
1. Configuration Management
`python
Application configuration
config = { "database": { "host": "localhost", "port": 5432, "name": "myapp", "user": "admin" }, "cache": { "type": "redis", "ttl": 3600 }, "logging": { "level": "INFO", "file": "app.log" } }def get_config_value(path, default=None): """Get configuration value using dot notation""" keys = path.split(".") value = config for key in keys: if isinstance(value, dict) and key in value: value = value[key] else: return default return value
Usage examples
db_host = get_config_value("database.host") log_level = get_config_value("logging.level", "DEBUG") print(f"Database host: {db_host}") print(f"Log level: {log_level}")`2. Caching and Memoization
`python
Simple cache implementation
cache = {}def expensive_function(n): """Simulates an expensive computation""" if n in cache: print(f"Cache hit for {n}") return cache[n] print(f"Computing for {n}") result = sum(i2 for i in range(n)) cache[n] = result return result
Test caching
print(expensive_function(1000)) # Computed print(expensive_function(1000)) # Cache hitDecorator-based memoization
def memoize(func): cache = {} def wrapper(*args): if args in cache: return cache[args] result = func(*args) cache[args] = result return result return wrapper@memoize def fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2)
print(f"Fibonacci(30): {fibonacci(30)}")
`
3. Data Transformation and Aggregation
`python
Sales data processing
sales_data = [ {"product": "laptop", "category": "electronics", "amount": 1200, "region": "north"}, {"product": "mouse", "category": "electronics", "amount": 25, "region": "south"}, {"product": "desk", "category": "furniture", "amount": 300, "region": "north"}, {"product": "chair", "category": "furniture", "amount": 150, "region": "south"}, {"product": "phone", "category": "electronics", "amount": 800, "region": "north"} ]Group by category
category_sales = {} for sale in sales_data: category = sale["category"] if category not in category_sales: category_sales[category] = [] category_sales[category].append(sale)Calculate totals by category
category_totals = {} for category, sales in category_sales.items(): category_totals[category] = sum(sale["amount"] for sale in sales)print("Sales by category:") for category, total in category_totals.items(): print(f"{category}: ${total}")
Using dictionary comprehension for aggregation
region_totals = {} for sale in sales_data: region = sale["region"] region_totals[region] = region_totals.get(region, 0) + sale["amount"]print("Sales by region:")
for region, total in region_totals.items():
print(f"{region}: ${total}")
`
Best Practices
1. Key Design Guidelines
`python
Good: Use meaningful, consistent keys
user_profile = { "user_id": 12345, "username": "alice_doe", "email": "alice@example.com", "created_at": "2023-01-15", "is_active": True }Avoid: Inconsistent or unclear keys
bad_profile = {
"id": 12345,
"name": "alice_doe", # inconsistent with user_id
"mail": "alice@example.com", # abbreviated
"created": "2023-01-15", # incomplete
"active": True # inconsistent with is_active
}
`2. Error Handling
`python
def safe_dict_operations(data, key, default_value=None):
"""Demonstrate safe dictionary operations"""
# Safe key access
try:
value = data[key]
print(f"Found {key}: {value}")
except KeyError:
print(f"Key '{key}' not found")
value = default_value
# Alternative: using get()
value_safe = data.get(key, default_value)
# Safe nested access
def get_nested_value(dictionary, keys):
try:
for k in keys:
dictionary = dictionary[k]
return dictionary
except (KeyError, TypeError):
return None
return value_safe
Example usage
test_data = {"user": {"profile": {"name": "Alice"}}} name = safe_dict_operations(test_data, "user")`3. Memory Efficiency Tips
`python
Use __slots__ for dictionary-like classes when structure is fixed
class Point: __slots__ = ['x', 'y'] def __init__(self, x, y): self.x = x self.y = yFor large datasets, consider using arrays or numpy
import arrayInstead of: {i: i2 for i in range(1000000)}
Consider: array.array('i', [i2 for i in range(1000000)])
Use generators for large dictionary comprehensions when possible
def large_dict_generator(): return ((i, i2) for i in range(1000000))Convert to dict only when needed
large_dict = dict(large_dict_generator())
`4. Testing Dictionary Operations
`python
def test_dictionary_operations():
"""Test cases for dictionary operations"""
# Test data
test_dict = {"a": 1, "b": 2, "c": 3}
# Test key existence
assert "a" in test_dict
assert "d" not in test_dict
# Test value retrieval
assert test_dict["a"] == 1
assert test_dict.get("d", 0) == 0
# Test modification
test_dict["d"] = 4
assert len(test_dict) == 4
# Test deletion
del test_dict["d"]
assert len(test_dict) == 3
print("All dictionary tests passed!")
Run tests
test_dictionary_operations()`Conclusion
Python dictionaries are fundamental data structures that provide efficient key-value storage and retrieval. Their versatility makes them suitable for a wide range of applications, from simple data storage to complex data processing tasks. Understanding their characteristics, methods, and best practices is essential for writing efficient and maintainable Python code.
Key takeaways include understanding the hash table implementation for performance optimization, using appropriate methods for safe data access, leveraging dictionary comprehensions for concise code, and following best practices for key design and error handling. With these concepts mastered, dictionaries become powerful tools for solving various programming challenges efficiently.