Adding and Removing Dictionary Items in Python
Table of Contents
1. [Introduction to Dictionary Operations](#introduction) 2. [Adding Items to Dictionaries](#adding-items) 3. [Removing Items from Dictionaries](#removing-items) 4. [Advanced Dictionary Operations](#advanced-operations) 5. [Performance Considerations](#performance) 6. [Common Use Cases and Examples](#use-cases) 7. [Best Practices](#best-practices)Introduction to Dictionary Operations {#introduction}
Python dictionaries are mutable data structures that store key-value pairs. One of their most powerful features is the ability to dynamically add and remove items during program execution. Understanding these operations is crucial for effective Python programming, as dictionaries are frequently used for data storage, caching, configuration management, and many other applications.
Dictionary operations for adding and removing items are fundamental to maintaining dynamic data structures. These operations allow programs to respond to changing requirements, user input, and evolving data sets. The efficiency and flexibility of these operations make dictionaries one of the most versatile data structures in Python.
Adding Items to Dictionaries {#adding-items}
Direct Assignment Method
The most straightforward way to add items to a dictionary is through direct assignment using square bracket notation. This method allows you to add single key-value pairs or modify existing values.
`python
Creating an empty dictionary
student_grades = {}Adding items using direct assignment
student_grades['Alice'] = 95 student_grades['Bob'] = 87 student_grades['Charlie'] = 92print(student_grades)
Output: {'Alice': 95, 'Bob': 87, 'Charlie': 92}
Modifying existing values
student_grades['Alice'] = 98 print(student_grades)Output: {'Alice': 98, 'Bob': 87, 'Charlie': 92}
`Notes: - Direct assignment overwrites existing values if the key already exists - This method is the most efficient for adding single items - Keys must be immutable types (strings, numbers, tuples)
Using the update() Method
The update() method provides multiple ways to add items to a dictionary. It can accept another dictionary, an iterable of key-value pairs, or keyword arguments.
`python
Starting with a base dictionary
inventory = {'apples': 50, 'bananas': 30}Method 1: Update with another dictionary
new_items = {'oranges': 25, 'grapes': 40} inventory.update(new_items) print(inventory)Output: {'apples': 50, 'bananas': 30, 'oranges': 25, 'grapes': 40}
Method 2: Update with keyword arguments
inventory.update(pears=35, mangoes=20) print(inventory)Output: {'apples': 50, 'bananas': 30, 'oranges': 25, 'grapes': 40, 'pears': 35, 'mangoes': 20}
Method 3: Update with iterable of pairs
additional_items = [('kiwis', 15), ('strawberries', 60)] inventory.update(additional_items) print(inventory)Output: {'apples': 50, 'bananas': 30, 'oranges': 25, 'grapes': 40, 'pears': 35, 'mangoes': 20, 'kiwis': 15, 'strawberries': 60}
`Notes:
- update() can add multiple items simultaneously
- Existing keys will be overwritten with new values
- More efficient than multiple direct assignments for bulk operations
Using the setdefault() Method
The setdefault() method adds an item only if the key doesn't already exist. If the key exists, it returns the existing value without modification.
`python
Initialize dictionary
user_preferences = {'theme': 'dark', 'language': 'english'}Add new key with default value
result1 = user_preferences.setdefault('notifications', True) print(f"Added notifications: {result1}") print(user_preferences)Output: Added notifications: True
Output: {'theme': 'dark', 'language': 'english', 'notifications': True}
Attempt to add existing key
result2 = user_preferences.setdefault('theme', 'light') print(f"Theme remains: {result2}") print(user_preferences)Output: Theme remains: dark
Output: {'theme': 'dark', 'language': 'english', 'notifications': True}
`Notes:
- setdefault() prevents accidental overwriting of existing values
- Returns the value associated with the key (existing or newly set)
- Useful for initializing nested data structures
Dictionary Comprehensions for Adding Items
Dictionary comprehensions provide a concise way to create new dictionaries or add computed items based on existing data.
`python
Create a dictionary with computed values
numbers = [1, 2, 3, 4, 5] squares = {num: num2 for num in numbers} print(squares)Output: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
Adding items based on conditions
original_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4} filtered_and_modified = {k: v*2 for k, v in original_dict.items() if v > 2} print(filtered_and_modified)Output: {'c': 6, 'd': 8}
Combining with existing dictionary
base_config = {'debug': False, 'version': '1.0'} extended_config = {base_config, {'timeout': 30, 'retries': 3}} print(extended_config)Output: {'debug': False, 'version': '1.0', 'timeout': 30, 'retries': 3}
`Removing Items from Dictionaries {#removing-items}
Using the del Statement
The del statement removes items by specifying their keys. It raises a KeyError if the key doesn't exist.
`python
Sample dictionary
product_catalog = { 'laptop': 999.99, 'mouse': 25.50, 'keyboard': 75.00, 'monitor': 299.99, 'speakers': 89.99 }Remove single item
del product_catalog['speakers'] print(product_catalog)Output: {'laptop': 999.99, 'mouse': 25.50, 'keyboard': 75.00, 'monitor': 299.99}
Attempting to delete non-existent key raises KeyError
try: del product_catalog['webcam'] except KeyError as e: print(f"Key not found: {e}")Output: Key not found: 'webcam'
`Notes:
- del permanently removes the key-value pair
- Raises KeyError for non-existent keys
- Most efficient method for removing known keys
Using the pop() Method
The pop() method removes and returns the value associated with a specified key. It can accept a default value to return if the key doesn't exist.
`python
Sample dictionary
server_config = { 'host': 'localhost', 'port': 8080, 'debug': True, 'ssl': False, 'timeout': 30 }Remove and get value
removed_debug = server_config.pop('debug') print(f"Removed debug setting: {removed_debug}") print(server_config)Output: Removed debug setting: True
Output: {'host': 'localhost', 'port': 8080, 'ssl': False, 'timeout': 30}
Pop with default value for non-existent key
max_connections = server_config.pop('max_connections', 100) print(f"Max connections (default): {max_connections}") print(server_config)Output: Max connections (default): 100
Output: {'host': 'localhost', 'port': 8080, 'ssl': False, 'timeout': 30}
Pop without default for non-existent key raises KeyError
try: server_config.pop('database_url') except KeyError as e: print(f"Key not found: {e}")`Notes:
- pop() returns the removed value, making it useful for processing
- Default parameter prevents KeyError exceptions
- Combines removal and value retrieval in one operation
Using the popitem() Method
The popitem() method removes and returns an arbitrary key-value pair from the dictionary. In Python 3.7+, it removes the last inserted item due to insertion order preservation.
`python
Sample dictionary
task_queue = { 'task_1': 'process_data', 'task_2': 'send_email', 'task_3': 'backup_files', 'task_4': 'generate_report' }print("Original queue:", task_queue)
Remove last item (LIFO - Last In, First Out)
removed_task = task_queue.popitem() print(f"Removed task: {removed_task}") print("Updated queue:", task_queue)Output: Removed task: ('task_4', 'generate_report')
Output: Updated queue: {'task_1': 'process_data', 'task_2': 'send_email', 'task_3': 'backup_files'}
Remove another item
another_task = task_queue.popitem() print(f"Removed task: {another_task}") print("Updated queue:", task_queue)Output: Removed task: ('task_3', 'backup_files')
Output: Updated queue: {'task_1': 'process_data', 'task_2': 'send_email'}
popitem() on empty dictionary raises KeyError
empty_dict = {} try: empty_dict.popitem() except KeyError: print("Cannot pop from empty dictionary")`Notes:
- popitem() removes the last inserted item in Python 3.7+
- Useful for implementing stack-like behavior (LIFO)
- Raises KeyError when called on empty dictionary
Using the clear() Method
The clear() method removes all items from the dictionary, making it empty.
`python
Sample dictionary
cache_data = { 'user_123': {'name': 'John', 'last_login': '2024-01-15'}, 'user_456': {'name': 'Jane', 'last_login': '2024-01-14'}, 'user_789': {'name': 'Bob', 'last_login': '2024-01-13'} }print("Before clear:", len(cache_data)) print("Cache contents:", cache_data)
Clear all items
cache_data.clear()print("After clear:", len(cache_data)) print("Cache contents:", cache_data)
Output: After clear: 0
Output: Cache contents: {}
`Notes:
- clear() removes all key-value pairs efficiently
- The dictionary object remains the same, only its contents are removed
- More efficient than deleting and recreating the dictionary
Advanced Dictionary Operations {#advanced-operations}
Conditional Removal with Dictionary Comprehension
Dictionary comprehensions can be used to create new dictionaries with items removed based on conditions.
`python
Original data
sales_data = { 'January': 15000, 'February': 18000, 'March': 12000, 'April': 22000, 'May': 8000, 'June': 25000 }Remove months with sales below threshold
threshold = 15000 filtered_sales = {month: sales for month, sales in sales_data.items() if sales >= threshold} print("Sales above threshold:", filtered_sales)Output: Sales above threshold: {'January': 15000, 'February': 18000, 'April': 22000, 'June': 25000}
Remove items based on key characteristics
user_data = { 'admin_user': {'role': 'admin', 'active': True}, 'guest_user': {'role': 'guest', 'active': False}, 'power_user': {'role': 'power', 'active': True}, 'temp_admin': {'role': 'admin', 'active': False} }Keep only active users
active_users = {username: data for username, data in user_data.items() if data['active']} print("Active users:", active_users)Output: Active users: {'admin_user': {'role': 'admin', 'active': True}, 'power_user': {'role': 'power', 'active': True}}
`Bulk Operations with Multiple Methods
Combining different methods for complex operations on dictionaries.
`python
class InventoryManager:
def __init__(self):
self.inventory = {}
def add_items(self, items_dict):
"""Add multiple items using update()"""
self.inventory.update(items_dict)
return f"Added {len(items_dict)} items"
def remove_items(self, item_names):
"""Remove multiple items and return removed items"""
removed_items = {}
for name in item_names:
if name in self.inventory:
removed_items[name] = self.inventory.pop(name)
return removed_items
def remove_low_stock(self, threshold):
"""Remove items below stock threshold"""
to_remove = [name for name, quantity in self.inventory.items() if quantity < threshold]
return self.remove_items(to_remove)
def get_inventory(self):
return self.inventory.copy()
Example usage
manager = InventoryManager()Add initial inventory
initial_stock = { 'apples': 100, 'bananas': 5, 'oranges': 75, 'grapes': 2, 'pears': 50 }print(manager.add_items(initial_stock)) print("Current inventory:", manager.get_inventory())
Remove low stock items
removed_low_stock = manager.remove_low_stock(10) print("Removed low stock items:", removed_low_stock) print("Updated inventory:", manager.get_inventory())Remove specific items
specific_removals = manager.remove_items(['apples', 'pears']) print("Specifically removed:", specific_removals) print("Final inventory:", manager.get_inventory())`Performance Considerations {#performance}
Time Complexity Analysis
Understanding the performance characteristics of different dictionary operations is crucial for writing efficient code.
| Operation | Method | Time Complexity | Space Complexity | Notes |
|-----------|--------|----------------|------------------|-------|
| Add single item | dict[key] = value | O(1) average | O(1) | Most efficient for single additions |
| Add multiple items | dict.update() | O(n) | O(n) | n = number of items being added |
| Remove single item | del dict[key] | O(1) average | O(1) | Fastest removal method |
| Remove with return | dict.pop(key) | O(1) average | O(1) | Slightly slower due to return value |
| Remove last item | dict.popitem() | O(1) | O(1) | Efficient for LIFO operations |
| Clear all items | dict.clear() | O(n) | O(1) | n = number of items in dictionary |
| Conditional removal | Dictionary comprehension | O(n) | O(n) | Creates new dictionary |
Performance Comparison Example
`python
import time
import random
def performance_test(): # Setup test data test_size = 100000 test_dict = {f"key_{i}": random.randint(1, 1000) for i in range(test_size)} # Test direct assignment vs update for adding items new_items = {f"new_key_{i}": random.randint(1, 1000) for i in range(1000)} # Method 1: Direct assignment test_dict_1 = test_dict.copy() start_time = time.time() for key, value in new_items.items(): test_dict_1[key] = value direct_time = time.time() - start_time # Method 2: Update method test_dict_2 = test_dict.copy() start_time = time.time() test_dict_2.update(new_items) update_time = time.time() - start_time print(f"Direct assignment time: {direct_time:.6f} seconds") print(f"Update method time: {update_time:.6f} seconds") print(f"Update is {direct_time/update_time:.2f}x faster") # Test removal methods keys_to_remove = [f"key_{i}" for i in range(0, 1000, 10)] # Method 1: del statement test_dict_3 = test_dict.copy() start_time = time.time() for key in keys_to_remove: if key in test_dict_3: del test_dict_3[key] del_time = time.time() - start_time # Method 2: pop method test_dict_4 = test_dict.copy() start_time = time.time() for key in keys_to_remove: test_dict_4.pop(key, None) pop_time = time.time() - start_time print(f"Del statement time: {del_time:.6f} seconds") print(f"Pop method time: {pop_time:.6f} seconds") print(f"Del is {pop_time/del_time:.2f}x faster than pop")
Run performance test
performance_test()`Common Use Cases and Examples {#use-cases}
Configuration Management
Dictionaries are commonly used for managing application configurations, where settings need to be added, modified, or removed dynamically.
`python
class ConfigManager:
def __init__(self):
self.config = {
'database': {
'host': 'localhost',
'port': 5432,
'name': 'myapp'
},
'cache': {
'enabled': True,
'ttl': 3600
},
'logging': {
'level': 'INFO',
'file': '/var/log/app.log'
}
}
def set_config(self, path, value):
"""Set configuration value using dot notation path"""
keys = path.split('.')
current = self.config
# Navigate to the parent of target key
for key in keys[:-1]:
current = current.setdefault(key, {})
# Set the final value
current[keys[-1]] = value
def remove_config(self, path):
"""Remove configuration value using dot notation path"""
keys = path.split('.')
current = self.config
try:
# Navigate to the parent of target key
for key in keys[:-1]:
current = current[key]
# Remove the final key
removed_value = current.pop(keys[-1])
return removed_value
except KeyError:
return None
def get_config(self, path=None):
"""Get configuration value or entire config"""
if path is None:
return self.config
keys = path.split('.')
current = self.config
try:
for key in keys:
current = current[key]
return current
except KeyError:
return None
def merge_config(self, new_config):
"""Merge new configuration with existing"""
def deep_merge(base, update):
for key, value in update.items():
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
deep_merge(base[key], value)
else:
base[key] = value
deep_merge(self.config, new_config)
Example usage
config_manager = ConfigManager()Add new configuration
config_manager.set_config('api.timeout', 30) config_manager.set_config('api.retries', 3)Merge additional configuration
additional_config = { 'database': { 'pool_size': 10 }, 'security': { 'encryption': True, 'key_rotation': 86400 } } config_manager.merge_config(additional_config)print("Updated configuration:") print(config_manager.get_config())
Remove configuration
removed = config_manager.remove_config('cache.ttl') print(f"Removed cache.ttl: {removed}") print("Final configuration:") print(config_manager.get_config())`Caching System
Implementing a simple caching system that adds and removes items based on usage patterns.
`python
import time
from collections import OrderedDict
class LRUCache: def __init__(self, capacity): self.capacity = capacity self.cache = OrderedDict() self.access_times = {} def get(self, key): """Get item from cache and update access time""" if key in self.cache: # Move to end (most recently used) value = self.cache.pop(key) self.cache[key] = value self.access_times[key] = time.time() return value return None def put(self, key, value): """Add item to cache, removing oldest if necessary""" if key in self.cache: # Update existing item self.cache.pop(key) elif len(self.cache) >= self.capacity: # Remove least recently used item oldest_key = next(iter(self.cache)) self.cache.pop(oldest_key) self.access_times.pop(oldest_key, None) # Add new item self.cache[key] = value self.access_times[key] = time.time() def remove(self, key): """Manually remove item from cache""" removed_value = self.cache.pop(key, None) self.access_times.pop(key, None) return removed_value def clear_expired(self, max_age_seconds): """Remove items older than specified age""" current_time = time.time() expired_keys = [ key for key, access_time in self.access_times.items() if current_time - access_time > max_age_seconds ] removed_items = {} for key in expired_keys: removed_items[key] = self.remove(key) return removed_items def get_stats(self): """Get cache statistics""" return { 'size': len(self.cache), 'capacity': self.capacity, 'items': list(self.cache.keys()) }
Example usage
cache = LRUCache(capacity=3)Add items
cache.put('user_1', {'name': 'Alice', 'role': 'admin'}) cache.put('user_2', {'name': 'Bob', 'role': 'user'}) cache.put('user_3', {'name': 'Charlie', 'role': 'user'})print("Cache after adding 3 items:", cache.get_stats())
Add fourth item (should remove oldest)
cache.put('user_4', {'name': 'Diana', 'role': 'admin'}) print("Cache after adding 4th item:", cache.get_stats())Access an item (moves to end)
user_data = cache.get('user_2') print(f"Retrieved user_2: {user_data}") print("Cache after accessing user_2:", cache.get_stats())Simulate time passage and clear expired items
time.sleep(1) expired = cache.clear_expired(0.5) print(f"Expired items: {expired}") print("Cache after clearing expired:", cache.get_stats())`Data Processing Pipeline
Using dictionaries for data transformation where items are added, processed, and removed based on business logic.
`python
class DataProcessor:
def __init__(self):
self.raw_data = {}
self.processed_data = {}
self.failed_data = {}
self.processing_stats = {
'total_received': 0,
'successfully_processed': 0,
'failed_processing': 0
}
def add_raw_data(self, data_id, data):
"""Add raw data for processing"""
self.raw_data[data_id] = {
'data': data,
'timestamp': time.time(),
'attempts': 0
}
self.processing_stats['total_received'] += 1
def process_item(self, data_id):
"""Process a single data item"""
if data_id not in self.raw_data:
return False
item = self.raw_data[data_id]
item['attempts'] += 1
try:
# Simulate processing logic
raw_value = item['data']
# Example processing: convert to uppercase if string, multiply by 2 if number
if isinstance(raw_value, str):
processed_value = raw_value.upper()
elif isinstance(raw_value, (int, float)):
processed_value = raw_value * 2
else:
raise ValueError(f"Unsupported data type: {type(raw_value)}")
# Move to processed data
self.processed_data[data_id] = {
'original': raw_value,
'processed': processed_value,
'processed_at': time.time(),
'attempts': item['attempts']
}
# Remove from raw data
self.raw_data.pop(data_id)
self.processing_stats['successfully_processed'] += 1
return True
except Exception as e:
if item['attempts'] >= 3:
# Move to failed data after 3 attempts
self.failed_data[data_id] = {
'data': item['data'],
'error': str(e),
'attempts': item['attempts'],
'failed_at': time.time()
}
self.raw_data.pop(data_id)
self.processing_stats['failed_processing'] += 1
return False
def process_all_pending(self):
"""Process all items in raw_data queue"""
pending_ids = list(self.raw_data.keys())
results = {}
for data_id in pending_ids:
results[data_id] = self.process_item(data_id)
return results
def remove_old_processed(self, max_age_seconds):
"""Remove processed items older than specified age"""
current_time = time.time()
old_items = {}
for data_id, item in list(self.processed_data.items()):
if current_time - item['processed_at'] > max_age_seconds:
old_items[data_id] = self.processed_data.pop(data_id)
return old_items
def get_status(self):
"""Get current processing status"""
return {
'stats': self.processing_stats.copy(),
'queues': {
'raw_data': len(self.raw_data),
'processed_data': len(self.processed_data),
'failed_data': len(self.failed_data)
}
}
def clear_failed_data(self):
"""Clear all failed data items"""
cleared_count = len(self.failed_data)
self.failed_data.clear()
return cleared_count
Example usage
processor = DataProcessor()Add various types of data
test_data = { 'item_1': 'hello world', 'item_2': 42, 'item_3': 3.14, 'item_4': 'python programming', 'item_5': [1, 2, 3], # This will fail processing 'item_6': 'data processing' }for data_id, data in test_data.items(): processor.add_raw_data(data_id, data)
print("Status after adding data:", processor.get_status())
Process all items
processing_results = processor.process_all_pending() print("Processing results:", processing_results) print("Status after processing:", processor.get_status())Show processed data
print("\nProcessed data:") for data_id, item in processor.processed_data.items(): print(f" {data_id}: {item['original']} -> {item['processed']}")Show failed data
print("\nFailed data:") for data_id, item in processor.failed_data.items(): print(f" {data_id}: {item['data']} (Error: {item['error']})")Clean up old processed data
time.sleep(1) old_items = processor.remove_old_processed(0.5) print(f"\nRemoved {len(old_items)} old processed items")Clear failed data
cleared_count = processor.clear_failed_data() print(f"Cleared {cleared_count} failed items")print("Final status:", processor.get_status())
`
Best Practices {#best-practices}
Error Handling and Safety
When working with dictionary operations, proper error handling is essential to prevent runtime errors and ensure robust code.
`python
class SafeDictionaryOperations:
@staticmethod
def safe_add(dictionary, key, value, overwrite=True):
"""Safely add item to dictionary with optional overwrite protection"""
if not overwrite and key in dictionary:
return False, f"Key '{key}' already exists"
try:
dictionary[key] = value
return True, f"Successfully added '{key}'"
except Exception as e:
return False, f"Error adding '{key}': {str(e)}"
@staticmethod
def safe_remove(dictionary, key, default=None):
"""Safely remove item from dictionary with default return"""
try:
return True, dictionary.pop(key)
except KeyError:
if default is not None:
return False, default
return False, f"Key '{key}' not found"
@staticmethod
def safe_bulk_update(dictionary, updates, validate_keys=True):
"""Safely update dictionary with validation"""
if not isinstance(updates, dict):
return False, "Updates must be a dictionary"
if validate_keys:
# Validate all keys are hashable
for key in updates.keys():
try:
hash(key)
except TypeError:
return False, f"Key '{key}' is not hashable"
try:
original_size = len(dictionary)
dictionary.update(updates)
new_size = len(dictionary)
return True, f"Updated dictionary: {new_size - original_size} new keys added"
except Exception as e:
return False, f"Error during bulk update: {str(e)}"
@staticmethod
def safe_clear_with_backup(dictionary):
"""Clear dictionary with backup option"""
backup = dictionary.copy()
dictionary.clear()
return backup
Example usage of safe operations
test_dict = {'existing_key': 'existing_value'}Safe adding
success, message = SafeDictionaryOperations.safe_add(test_dict, 'new_key', 'new_value') print(f"Add result: {success}, Message: {message}")Safe adding with overwrite protection
success, message = SafeDictionaryOperations.safe_add(test_dict, 'existing_key', 'updated_value', overwrite=False) print(f"Protected add result: {success}, Message: {message}")Safe removal
success, value = SafeDictionaryOperations.safe_remove(test_dict, 'nonexistent_key', 'default_value') print(f"Remove result: {success}, Value: {value}")Safe bulk update
updates = {'key1': 'value1', 'key2': 'value2'} success, message = SafeDictionaryOperations.safe_bulk_update(test_dict, updates) print(f"Bulk update result: {success}, Message: {message}")print("Final dictionary:", test_dict)
`
Memory Management and Optimization
Efficient memory usage is important when working with large dictionaries or in memory-constrained environments.
`python
import sys
from collections import defaultdict
class OptimizedDictionaryManager: def __init__(self): self.data = {} self.memory_threshold = 1000000 # 1MB threshold def get_memory_usage(self): """Calculate approximate memory usage of the dictionary""" return sys.getsizeof(self.data) + sum( sys.getsizeof(key) + sys.getsizeof(value) for key, value in self.data.items() ) def add_with_memory_check(self, key, value): """Add item with memory usage monitoring""" # Estimate memory impact estimated_size = sys.getsizeof(key) + sys.getsizeof(value) current_usage = self.get_memory_usage() if current_usage + estimated_size > self.memory_threshold: # Trigger cleanup before adding self.cleanup_old_entries() self.data[key] = value return current_usage + estimated_size def cleanup_old_entries(self, keep_ratio=0.7): """Remove oldest entries to free memory""" current_size = len(self.data) target_size = int(current_size * keep_ratio) if current_size <= target_size: return 0 # Remove oldest entries (assuming insertion order matters) items_to_remove = current_size - target_size keys_to_remove = list(self.data.keys())[:items_to_remove] removed_count = 0 for key in keys_to_remove: self.data.pop(key, None) removed_count += 1 return removed_count def batch_remove_by_pattern(self, pattern_func): """Remove items based on pattern matching function""" keys_to_remove = [ key for key in self.data.keys() if pattern_func(key, self.data[key]) ] removed_items = {} for key in keys_to_remove: removed_items[key] = self.data.pop(key) return removed_items def get_statistics(self): """Get detailed statistics about the dictionary""" return { 'item_count': len(self.data), 'memory_usage_bytes': self.get_memory_usage(), 'memory_usage_mb': self.get_memory_usage() / (1024 * 1024), 'average_key_size': sum(sys.getsizeof(k) for k in self.data.keys()) / len(self.data) if self.data else 0, 'average_value_size': sum(sys.getsizeof(v) for v in self.data.values()) / len(self.data) if self.data else 0 }
Example usage
manager = OptimizedDictionaryManager()Add many items to test memory management
for i in range(1000): key = f"item_{i:04d}" value = f"data_{'x' * (i % 100)}" # Variable length values memory_used = manager.add_with_memory_check(key, value) if i % 200 == 0: stats = manager.get_statistics() print(f"After {i+1} items: {stats['item_count']} items, {stats['memory_usage_mb']:.2f} MB")Remove items based on pattern
def remove_pattern(key, value): # Remove items where key ends with even numbers return key.endswith(('0', '2', '4', '6', '8'))removed_items = manager.batch_remove_by_pattern(remove_pattern) print(f"Removed {len(removed_items)} items matching pattern")
Final statistics
final_stats = manager.get_statistics() print("Final statistics:", final_stats)`Thread Safety Considerations
When working with dictionaries in multi-threaded environments, proper synchronization is crucial.
`python
import threading
import time
from collections import defaultdict
from contextlib import contextmanager
class ThreadSafeDictionary: def __init__(self): self._data = {} self._lock = threading.RLock() # Reentrant lock self._operation_stats = defaultdict(int) @contextmanager def _thread_safe_operation(self, operation_name): """Context manager for thread-safe operations""" with self._lock: start_time = time.time() try: yield self._operation_stats[f"{operation_name}_success"] += 1 except Exception as e: self._operation_stats[f"{operation_name}_error"] += 1 raise finally: duration = time.time() - start_time self._operation_stats[f"{operation_name}_total_time"] += duration def set_item(self, key, value): """Thread-safe item setting""" with self._thread_safe_operation("set_item"): self._data[key] = value return True def get_item(self, key, default=None): """Thread-safe item retrieval""" with self._thread_safe_operation("get_item"): return self._data.get(key, default) def remove_item(self, key): """Thread-safe item removal""" with self._thread_safe_operation("remove_item"): return self._data.pop(key, None) def bulk_update(self, items): """Thread-safe bulk update""" with self._thread_safe_operation("bulk_update"): self._data.update(items) return len(items) def clear_all(self): """Thread-safe clear operation""" with self._thread_safe_operation("clear_all"): count = len(self._data) self._data.clear() return count def get_snapshot(self): """Get thread-safe snapshot of current data""" with self._thread_safe_operation("get_snapshot"): return self._data.copy() def get_stats(self): """Get operation statistics""" with self._lock: return dict(self._operation_stats) def atomic_increment(self, key, increment=1): """Atomically increment a numeric value""" with self._thread_safe_operation("atomic_increment"): current_value = self._data.get(key, 0) new_value = current_value + increment self._data[key] = new_value return new_value
Example usage with multiple threads
def worker_thread(thread_id, shared_dict, operations_per_thread): """Worker function for testing thread safety""" for i in range(operations_per_thread): # Mix of different operations key = f"thread_{thread_id}_item_{i}" # Add item shared_dict.set_item(key, f"value_{i}") # Increment counter shared_dict.atomic_increment(f"thread_{thread_id}_counter") # Sometimes remove items if i % 10 == 0 and i > 0: remove_key = f"thread_{thread_id}_item_{i-5}" shared_dict.remove_item(remove_key) # Brief pause to simulate real work time.sleep(0.001)Test thread safety
print("Testing thread-safe dictionary operations...") shared_dict = ThreadSafeDictionary()Create multiple threads
threads = [] num_threads = 5 operations_per_thread = 50start_time = time.time()
for thread_id in range(num_threads): thread = threading.Thread( target=worker_thread, args=(thread_id, shared_dict, operations_per_thread) ) threads.append(thread) thread.start()
Wait for all threads to complete
for thread in threads: thread.join()end_time = time.time()
Get final results
final_snapshot = shared_dict.get_snapshot() operation_stats = shared_dict.get_stats()print(f"Completed in {end_time - start_time:.2f} seconds") print(f"Final dictionary size: {len(final_snapshot)}") print("Operation statistics:") for operation, count in operation_stats.items(): if operation.endswith('_success'): print(f" {operation}: {count}")
Show counter values
counter_items = {k: v for k, v in final_snapshot.items() if 'counter' in k} print("Thread counters:", counter_items)`This comprehensive guide covers the essential aspects of adding and removing dictionary items in Python, providing practical examples, performance considerations, and best practices for real-world applications. The examples demonstrate various scenarios from simple operations to complex, thread-safe implementations suitable for production environments.