In-Depth Dive into Python Dictionaries and Sets
This article handles specialities of the most common Python data structures.
Dictionaries
While normal dictionaries dict
are the wide standard of dictionaries, they suffer some difficulties:
- They don’t save the order of elements added to the dict
- They don’t have default values of you access elements not in the dictionary
- Searching for a key in several dictionaries
- making key-value pairs read-only
This is obviously important in some special cases. Luckily, there are some workarounds.
Order Preservation
This isn’t an issue since Python 3.7. If you work with older Python versions, you can use collections.OrderedDict
:
from collections import OrderedDict
ordered_dict = OrderedDict()
ordered_dict[‘a’] = 1
ordered_dict[‘b’] = 2
print(ordered_dict) # Preserves insertion order
Default Values for Missing Pairs
One way is to use the setdefault
method for normal Python dict
, which only assigns a new value, if the key isn’t present yet.
But in Order for directly manipulating an element, which isn’t present yet, it is easier to set the default elements of a dictionary:
from collections import defaultdict
class RandomClass():
def __init__(self):
pass
def __str__(self):
return f"Random Object with {self.__dict__} as attributes"
default_dict = defaultdict(int) # Default value of 0 for missing keys
print(default_dict['missing_key']) # Outputs 0
# -------------------------------
default_dict = defaultdict(RandomClass) # Default value is the class RandomClass
print(default_dict['missing_key']) # Outputs Random Object with {} as attributes
#---------------------------------
my_dict = defaultdict(list)
# Automatically assigns an empty list if 'a' doesn't exist
my_dict['a'].append(1)
print(my_dict) # {'a': [1]}
This leads to cleaner code, than manually checking if an element exists via my_dict.setdefault('a', []).append(1)
.
Searching for a key across multiple dictionaries
If you don’t want to loop over multiple dictionaries, which is time-consuming and not so nice to read, then you can shorten this by using collections.ChainMap
:
from collections import ChainMap
dict1 = {'a': 1}
dict2 = {'b': 2}
chain = ChainMap(dict1, dict2)
print(chain['b']) # Searches through all dictionaries
Making key-value pairs read-only
This is done again with another library, collections.MappingProxyType
:
from types import MappingProxyType
my_dict = {'a': 1, 'b': 2}
read_only_dict = MappingProxyType(my_dict)
print(read_only_dict['a']) # Works
read_only_dict['a'] = 3 # Raises TypeError: 'mappingproxy' object does not support item assignment
Sets
A Python set
is a set of arbitrary hash-able elements. It also suffers from some difficulties:
- Sometimes it can be important that a set is non-changeable. Hence, these static sets are hashable and can be used as dictionary keys.
- Sometimes it is important to know, how often an element is in a set. This isn’t possible (or wanted) in normal sets.
Immutability: Frozen sets
# Example of using frozenset
immutable_set = frozenset([1, 2, 3, 4])
print(immutable_set)
# Try modifying it (will raise an error)
immutable_set.add(5) # This will raise an AttributeError
Counting element frequency: collections.Counter
from collections import Counter
# Example of using Counter
elements = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
frequency_counter = Counter(elements)
print(frequency_counter) # Output: Counter({4: 4, 3: 3, 2: 2, 1: 1})
print(frequency_counter[2]) # Frequency of element '2' -> Output: 2