Dictionary Best Practices

Coding standards and best practices for using dictionaries

Python Dictionary Best Practices

Python dictionaries are versatile data structures that can significantly improve code clarity and performance when used properly. This guide covers best practices, coding standards, and design patterns for effectively using dictionaries in Python.

Dictionary Creation

Use Dictionary Comprehensions for Transformations

✅ Good

# Dictionary comprehension is concise and clear
squares = {x: x**2 for x in range(10)}

# Transform a list into a dictionary
names = ['alice', 'bob', 'charlie']
name_lengths = {name: len(name) for name in names}

❌ Avoid

# Manual assignment is verbose and slower
squares = {}
for x in range(10):
    squares[x] = x**2

# Less efficient way to transform a list
name_lengths = {}
for name in names:
    name_lengths[name] = len(name)

Initialize with Default Values

✅ Good

# Initialize with fromkeys
categories = dict.fromkeys(['electronics', 'books', 'clothing'], 0)

# Dictionary literal is clear
default_preferences = {
    'theme': 'dark',
    'notifications': True,
    'language': 'en'
}

❌ Avoid

# Less efficient manual initialization
categories = {}
for cat in ['electronics', 'books', 'clothing']:
    categories[cat] = 0

# Unclear and verbose assignment
default_preferences = {}
default_preferences['theme'] = 'dark'
default_preferences['notifications'] = True
default_preferences['language'] = 'en'

Dictionary Access

Use get() for Safe Access

✅ Good

# Safe access with default value
user_role = user_data.get('role', 'guest')

# Avoid KeyError exceptions
value = my_dict.get('key')  # Returns None if key doesn't exist

❌ Avoid

# Risky direct access - may raise KeyError
user_role = user_data['role']  # Crashes if 'role' is missing

# Verbose exception handling
try:
    value = my_dict['key']
except KeyError:
    value = None

Check for Key Existence

✅ Good

# Use the 'in' operator
if 'username' in user_data:
    process_username(user_data['username'])

# Combine with get() for complex logic
if user_data.get('is_admin', False):
    grant_admin_privileges()

❌ Avoid

# Using problematic has_key() method (removed in Python 3)
if user_data.has_key('username'):  # Error in Python 3
    process_username(user_data['username'])

# Error-prone try/except for existence checking
try:
    process_username(user_data['username'])
except KeyError:
    pass

Dictionary Iteration

Iterate with Appropriate Methods

✅ Good

# Iterate over keys (most efficient if only keys needed)
for key in my_dict:
    print(key)

# Iterate over key-value pairs
for key, value in my_dict.items():
    print(f"{key}: {value}")

# When only values are needed
for value in my_dict.values():
    process_value(value)

❌ Avoid

# Inefficient key iteration with lookup
for key in my_dict:
    value = my_dict[key]  # Unnecessary lookup
    print(f"{key}: {value}")

# Don't use keys() unnecessarily
for key in my_dict.keys():  # Just use: for key in my_dict
    print(key)

# Avoid unpacking tuples manually
for item in my_dict.items():
    key = item[0]
    value = item[1]
    print(f"{key}: {value}")

Dictionary Merging and Updating

Use Modern Merging Techniques

✅ Good

# Python 3.9+ merge operator
merged = dict1 | dict2

# Python 3.5+ unpacking
merged = {**dict1, **dict2}

# In-place update
settings.update(user_settings)

❌ Avoid

# Manual merging is verbose and error-prone
merged = dict1.copy()
for key, value in dict2.items():
    merged[key] = value

# Don't reinvent the wheel
merged = {}
for d in [dict1, dict2]:
    for key, value in d.items():
        merged[key] = value

Common Dictionary Patterns

Grouping Data

# Group people by age
people_by_age = {}
for person in people:
    age = person['age']
    if age not in people_by_age:
        people_by_age[age] = []
    people_by_age[age].append(person)

# More concise with setdefault
people_by_age = {}
for person in people:
    people_by_age.setdefault(person['age'], []).append(person)

# Even better with defaultdict
from collections import defaultdict
people_by_age = defaultdict(list)
for person in people:
    people_by_age[person['age']].append(person)

Counting

# Count word frequency
word_counts = {}
for word in text.split():
    if word not in word_counts:
        word_counts[word] = 0
    word_counts[word] += 1

# Better with get()
word_counts = {}
for word in text.split():
    word_counts[word] = word_counts.get(word, 0) + 1

# Best with Counter
from collections import Counter
word_counts = Counter(text.split())

Caching/Memoization

# Basic memoization with dictionary
cache = {}
def fibonacci(n):
    if n in cache:
        return cache[n]
    if n <= 1:
        result = n
    else:
        result = fibonacci(n-1) + fibonacci(n-2)
    cache[n] = result
    return result

# Better with functools
from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

Key Types and Dictionary Design

Choose Appropriate Key Types

Key TypeGood ForNotes
StringsConfiguration, JSON data, named propertiesMost common dictionary key type
IntegersLookup tables, sparse arraysVery efficient hash function
TuplesMulti-dimensional lookups, composite keysElements must be immutable
FrozensetsWhen order doesn't matterGood for caching combinations
Custom classesDomain-specific keysMust implement __hash__ and __eq__

Custom Classes as Keys

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def __hash__(self):
        return hash((self.x, self.y))
        
    def __eq__(self, other):
        if not isinstance(other, Point):
            return False
        return self.x == other.x and self.y == other.y
        
# Now Points can be used as dictionary keys
point_data = {}
point_data[Point(1, 2)] = "Point A"
point_data[Point(3, 4)] = "Point B"

Code Style and Naming

Follow PEP 8 Guidelines

  • Use lowercase with underscores for variable names: user_settings
  • Prefer descriptive names that indicate what the dictionary represents: employee_salaries rather than data
  • Use singular or plural names based on whether the dictionary maps one item or multiple items to each key
  • Be consistent with naming patterns across your codebase

✅ Good

# Descriptive names
user_preferences = {'theme': 'dark', 'language': 'en'}
employee_to_manager = {42: 37, 43: 37, 44: 38}
word_counts = Counter(text.split())

# Consistent formatting
config = {
    'app_name': 'MyApp',
    'version': '1.0.0',
    'debug_mode': False,
    'max_connections': 100
}

❌ Avoid

# Vague names
d = {'theme': 'dark', 'language': 'en'}
mapping = {42: 37, 43: 37, 44: 38}
dict1 = Counter(text.split())

# Inconsistent formatting
config = {
  'AppName': 'MyApp',
    'Version': '1.0.0',
  'DebugMode': False,
    'maxConnections': 100
}

Special Dictionary Types

Python's collections module offers specialized dictionary types for specific use cases:

defaultdict

Creates dictionaries with default values for missing keys, eliminating the need for key existence checks.

from collections import defaultdict

# Dictionary of lists
groups = defaultdict(list)
for item in items:
    groups[item.category].append(item)

# Dictionary of counters
word_lengths = defaultdict(int)
for word in text.split():
    word_lengths[word] += 1

OrderedDict

Maintains keys in insertion order (though standard dict preserves order since Python 3.7+).

from collections import OrderedDict

# Create an ordered dictionary
cache = OrderedDict()
for key in access_order:
    cache[key] = get_value(key)

# Implementing an LRU cache
class LRUCache:
    def __init__(self, capacity):
        self.cache = OrderedDict()
        self.capacity = capacity
        
    def get(self, key):
        if key not in self.cache:
            return -1
        # Move to end (most recently used)
        value = self.cache.pop(key)
        self.cache[key] = value
        return value

Counter

Specialized dictionary for counting hashable objects, with handy methods for frequency analysis.

from collections import Counter

# Count word frequency
words = text.lower().split()
word_counts = Counter(words)

# Most common words
top_five = word_counts.most_common(5)

# Set operations
counter1 = Counter(['a', 'b', 'b', 'c'])
counter2 = Counter(['b', 'c', 'c', 'd'])

# Elements in both counters
print(counter1 & counter2)  # Counter({'b': 1, 'c': 1})

# All elements combined
print(counter1 | counter2)  # Counter({'c': 2, 'b': 2, 'a': 1, 'd': 1})

Summary of Best Practices

  • Use dictionary comprehensions for clear and concise dictionary creation
  • Choose get() over direct access for safer key lookups
  • Iterate with items(), keys(), or values() depending on your needs
  • Use modern merging techniques like | or {...dict1, ...dict2}
  • Consider specialized dictionary types for specific use cases
  • Follow consistent naming conventions and code style
  • Choose appropriate key types for your dictionaries
  • Optimize performance by pre-sizing and minimizing temporary dictionary creation