Dictionaries in Python are a fundamental data structure that stores an
arbitrary number of objects, each identified by a unique key.
They are also known by various other names, such as maps,
hashmaps, lookup tables, or
associative arrays.
my_dict = {
'name': 'John',
'age': 25,
'city': 'New York'
}
dict: Your Go-to Dictionary
Description:
Standard dictionary in Python, which stores key-value pairs.
Usage:
Efficient for fast lookups, inserts, updates, and deletions.
Example:
my_dict = {'apple': 1, 'banana': 2}
print(my_dict['apple']) # Output: 1
Description: A dictionary subclass that maintains the order in which keys were first inserted.
Usage: Useful when the order of items matters.
Example:
from collections import OrderedDict
od = OrderedDict()
od['apple'] = 1
od['banana'] = 2
for key in od:
print(key) # Output: apple, banana
Description: A dictionary subclass that provides default values for missing keys.
Usage: Useful for counting, grouping, or accumulating values.
Example:
from collections import defaultdict
dd = defaultdict(int)
dd['apple'] += 1
print(dd['apple']) # Output: 1
print(dd['banana']) # Output: 0 (default int value)
Description: Combines multiple dictionaries into a single view.
Usage: Useful for managing nested scopes or combining configurations.
Example:
from collections import ChainMap
dict1 = {'apple': 1}
dict2 = {'banana': 2}
chain = ChainMap(dict1, dict2)
print(chain['apple']) # Output: 1
print(chain['banana']) # Output: 2
Description: Provides a read-only view of a dictionary.
Usage: Useful for creating immutable dictionaries.
Example:
from types import MappingProxyType
original_dict = {'apple': 1}
proxy_dict = MappingProxyType(original_dict)
print(proxy_dict['apple']) # Output: 1
# proxy_dict['banana'] = 2 # Raises TypeError
An array is a fundamental data structure available in most programming languages, and it has a wide range of uses across different algorithms.
In this section, you’ll take a look at array implementations in Python that use only core language features or functionality that’s included in the Python standard library. You’ll see the strengths and weaknesses of each approach so you can decide which implementation is right for your use case.
Use Cases:
Usage:
my_list = [1, 2, 3, "hello"]
Common Methods: append(),
extend(), insert(),
remove(),pop(), sort(),
reverse()
Usage:
my_tuple = (1, 2, 3, "hello")
count(),
index()
from array import array
my_array = array('i', [1, 2, 3, 4])
append(),extend(), insert(),
remove(),pop()
Usage:
my_str = "hello"
upper(),
lower(),find(),replace(),
split(),join()
Usage:
my_bytes = b'hello'
Characteristics:
my_bytearray = bytearray(b'hello')
Python offers several data types that you can use to implement records, structs, and data transfer objects. In this section, you’ll get a quick look at each implementation and its unique characteristics. At the end, you’ll find a summary and a decision-making guide that will help you make your own picks.
dict)
Dictionaries are mutable and versatile data structures used to store key-value pairs.
person = {
"name": "Alice",
"age": 30,
"city": "New York"
}
print(person["name"]) # Output: Alice
tuple)
Tuples are immutable sequences used to store a collection of items. Once created, their contents cannot be changed.
coordinates = (10, 20)
print(coordinates[0]) # Output: 10
Creating a custom class provides more control over the data structure and behavior, allowing encapsulation and additional methods.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"My name is {self.name} and I am {self.age} years old."
person = Person("Alice", 30)
print(person.introduce()) # Output: My name is Alice and I am 30 years old.
dataclasses.dataclass)
Data classes, introduced in Python 3.7, simplify the creation of classes
used primarily for storing data. They automatically add special methods
like
__init__, __repr__, and __eq__.
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
person = Person("Alice", 30)
print(person) # Output: Person(name='Alice', age=30)
collections.namedtuple)
Named tuples are a subclass of tuples that allow for named fields, improving code readability.
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age'])
person = Person(name="Alice", age=30)
print(person.name) # Output: Alice
typing.NamedTuple)
typing.NamedTuple provides a more robust way to define named
tuples, allowing for type hints and more control.
from typing import NamedTuple
class Person(NamedTuple):
name: str
age: int
person = Person(name="Alice", age=30)
print(person.age) # Output: 30
struct.Struct)
The struct module allows for the conversion between Python
values and C structs represented as Python bytes objects.
import struct
data = struct.pack('i5s', 42, b'Alice')
print(data) # Output: b'*\x00\x00\x00Alice'
types.SimpleNamespace)
SimpleNamespace provides a flexible way to create objects
that can have arbitrary attributes added to them.
from types import SimpleNamespace
person = SimpleNamespace(name="Alice", age=30)
print(person.name) # Output: Alice
A set is an unordered collection of objects that doesn’t allow duplicate elements. Typically, sets are used to quickly test a value for membership in the set, to insert or delete new values from a set, and to compute the union or intersection of two sets.
Sets are unordered collections of unique elements. They are mutable and provide efficient membership testing, union, intersection, and difference operations.
Characteristics:
# Creating a set
fruits = {"apple", "banana", "cherry"}
print(fruits) # Output: {'banana', 'apple', 'cherry'}
Frozensets are immutable versions of sets. Once created, their elements cannot be modified. This makes frozensets useful as dictionary keys or in other contexts where immutability is required.
Characteristics:
# Creating a frozenset
immutable_fruits = frozenset({"apple", "banana", "cherry"})
print(immutable_fruits) # Output: frozenset({'banana', 'apple', 'cherry'})
# Attempting to add an element raises an error
# immutable_fruits.add("orange") # AttributeError
# Using frozenset as a dictionary key
frozen_set_dict = {immutable_fruits: "fruit set"}
print(frozen_set_dict) # Output: {frozenset({'banana', 'apple', 'cherry'}): 'fruit set'}
collections.Counter is a specialized dictionary subclass used
to count hashable objects. It functions as a multiset, allowing multiple
occurrences of elements and providing methods for common multiset
operations.
Characteristics:
from collections import Counter
# Creating a Counter
fruit_counts = Counter(["apple", "banana", "apple", "orange", "banana", "banana"])
print(fruit_counts) # Output: Counter({'banana': 3, 'apple': 2, 'orange': 1})
# Accessing counts
print(fruit_counts["banana"]) # Output: 3
# Most common elements
print(fruit_counts.most_common(2)) # Output: [('banana', 3), ('apple', 2)]
# Subtracting counts
fruit_counts.subtract(["banana", "apple"])
print(fruit_counts) # Output: Counter({'banana': 2, 'apple': 1, 'orange': 1})
A stack is a collection of objects that supports fast Last-In/First-Out
(LIFO) semantics for inserts and deletes. Unlike lists or arrays, stacks
typically don’t allow for random access to the objects they contain. The
insert and delete operations are also often called push and pop. Here is
an overview of stack implementations in Python, focusing on lists,
collections.deque, and queue.LifoQueue, along
with their characteristics and examples.
Characteristics:
append() and pop().
# Creating a stack using a list
stack = []
# Pushing elements onto the stack
stack.append(1)
stack.append(2)
stack.append(3)
print(stack) # Output: [1, 2, 3]
# Popping an element from the stack
top = stack.pop()
print(top) # Output: 3
print(stack) # Output: [1, 2]
Characteristics:
deque (double-ended queue) is part of the
collections module.
from collections import deque
s = deque()
s.append("eat")
s.append("sleep")
s.append("code")
print(s) # deque(['eat', 'sleep', 'code'])
s.pop() #'code'
s.pop() #'sleep'
s.pop() #'eat'
s.pop()
# Output:
# Traceback (most recent call last):
# File "", line 1, in
# IndexError: pop from an empty deque
Characteristics:
LifoQueue is part of the queue module and is
thread-safe.
from queue import LifoQueue
s = LifoQueue()
s.put("eat")
s.put("sleep")
s.put("code")
s
# <queue.LifoQueue object at 0x108298dd8>
s.get()
# 'code'
s.get()
# 'sleep'
s.get()
# 'eat'
s.get_nowait()
queue.Empty
s.get() # Blocks/waits forever...
A queue is a collection of objects that supports fast FIFO semantics for
inserts and deletes. The insert and delete operations are sometimes called
enqueue and dequeue. Unlike lists or arrays, queues typically don’t allow
for random access to the objects they contain.Here is an overview of queue
implementations in Python, focusing on lists,
collections.deque, queue.Queue, and
multiprocessing.Queue, along with their characteristics.
Characteristics:
append() and
pop(0), but this results in O(n) time complexity for pop
operations.
q = []
q.append("eat")
q.append("sleep")
q.append("code")
q
# ['eat', 'sleep', 'code']
# Careful: This is slow!
q.pop(0)
# 'eat'
Characteristics:
deque (double-ended queue) is part of the
collections module.
from collections import deque
q = deque()
q.append("eat")
q.append("sleep")
q.append("code")
q
# deque(['eat', 'sleep', 'code'])
q.popleft()
# 'eat'
q.popleft()
# 'sleep'
q.popleft()
# 'code'
q.popleft()
# Output:
Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# IndexError: pop from an empty deque
Characteristics:
Queue is part of the queue module and provides
thread-safe operations.
from queue import Queue
q = Queue()
q.put("eat")
q.put("sleep")
q.put("code")
q
# <queue.Queue object at 0x1070f5b38>
q.get()
# 'eat'
q.get()
# 'sleep'
q.get()
# 'code'
q.get_nowait()
queue.Empty
q.get() # Blocks/waits forever...
Characteristics:
multiprocessing.Queue is designed for sharing data between
processes.
from multiprocessing import Queue
q = Queue()
q.put("eat")
q.put("sleep")
q.put("code")
q
# <multiprocessing.queues.Queue object at 0x1081c12b0>
q.get()
# 'eat'
q.get()
# 'sleep'
q.get()
# 'code'
q.get() # Blocks/waits forever...
A priority queue is a container data structure that manages a set of
records with totally-ordered keys to provide quick access to the record
with the smallest or largest key in the set.pPiority queue as a modified
queue. Instead of retrieving the next element by insertion time, it
retrieves the highest-priority element. The priority of individual
elements is decided by the order applied to their keys. Here is an
overview of priority queue implementations in Python, focusing on lists,
heapq, and queue.PriorityQueue, along with a
summary of priority queues in Python.
Characteristics:
q = []
q.append((2, "code"))
q.append((1, "eat"))
q.append((3, "sleep"))
# Remember to re-sort every time a new element is inserted,
# or use bisect.insort()
q.sort(reverse=True)
while q:
next_item = q.pop()
print(next_item)
# Output:
(1, 'eat')
(2, 'code')
(3, 'sleep')
Characteristics:
heapq is a module that provides an efficient implementation
of the min-heap algorithm.
q = []
q.append((2, "code"))
q.append((1, "eat"))
q.append((3, "sleep"))
# Remember to re-sort every time a new element is inserted,
# or use bisect.insort()
q.sort(reverse=True)
while q:
next_item = q.pop()
print(next_item)
# Output:
(1, 'eat')
(2, 'code')
(3, 'sleep')
Characteristics:
PriorityQueue is part of the queue module and
provides a thread-safe implementation of priority queues.
from queue import PriorityQueue
q = PriorityQueue()
q.put((2, "code"))
q.put((1, "eat"))
q.put((3, "sleep"))
while not q.empty():
next_item = q.get()
print(next_item)
Output:
(1, 'eat')
(2, 'code')
(3, 'sleep')
In summary, Python offers a rich set of data structures, each designed to address specific needs and use cases. Understanding these structures is essential for writing efficient and effective code. Here's a brief recap of the main categories discussed: