2.6. Data Structures in Python#

In programming, a data structure is a specialized format for organizing, processing, retrieving, and storing data. They are fundamental building blocks that allow you to manage data efficiently and effectively. Choosing the right data structure for a given task is crucial for writing performant, readable, and maintainable code.

Python offers several powerful built-in data structures, each with unique properties and use cases. Understanding their characteristics is key to leveraging Python’s full potential.

2.6.1. Lists#

A list is Python’s most versatile and widely used ordered collection of items.

  • Definition/Purpose: A sequence of items, similar to an array in other languages, but with greater flexibility.

  • Key Characteristics:

    • Ordered: Items are stored in a defined sequence based on their insertion order. This order will not change unless explicitly modified.

    • Mutable: You can change, add, or remove items after the list has been created. This “changeability” is a primary distinction from tuples.

    • Allows Duplicates: Can contain multiple items with the same value.

    • Indexed: Items can be accessed using an integer index, starting from 0 for the first element. Negative indexing allows access from the end (-1 for the last element).

    • Slicing: Supports slicing to extract sub-sequences of elements.

    • Heterogeneous: Can contain items of different data types within the same list (integers, strings, floats, booleans, other lists, tuples, dictionaries, etc.).

    • Dynamic Size: Can grow or shrink as needed.

  • Syntax: [item1, item2, item3, ...]

# Creating a list
empty_list = []
my_numbers = [10, 20, 30, 40, 50]
mixed_list = ["apple", 1, True, 3.14, [6, 7]] # A list containing other data types

print(f"Empty list: {empty_list}")
print(f"List of numbers: {my_numbers}")
print(f"Mixed list: {mixed_list}")
Empty list: []
List of numbers: [10, 20, 30, 40, 50]
Mixed list: ['apple', 1, True, 3.14, [6, 7]]

2.6.1.1. Common List Methods#

Python’s lists come with a rich set of built-in methods for manipulation. Remember that many of these methods modify the list in-place and do not return a new list.

  • append(item): Adds a single item to the end of the list.

  • extend(iterable): Adds all items from an iterable (like another list) to the end of the list.

  • insert(index, item): Inserts an item at a specific index.

  • remove(value): Removes the first occurrence of a specified value. Raises a ValueError if the value is not found.

  • pop([index]): Removes the item at the given index and returns it. If no index is specified, it removes and returns the last item.

  • clear(): Removes all items from the list, making it empty.

  • index(value): Returns the index of the first occurrence of a value. Raises a ValueError if the value is not found.

  • count(value): Returns the number of times a value appears in the list.

  • sort(): Sorts the list’s items in-place in ascending order. You can use reverse=True for descending order.

  • reverse(): Reverses the order of the list’s items in-place.

  • copy(): Returns a shallow copy of the list. This is important to avoid modifying the original list unintentionally.

fruits = ["apple", "banana", "cherry"]
print(f"Initial list: {fruits}")

# append()
fruits.append("date")
print(f"After append('date'): {fruits}") # Output: ['apple', 'banana', 'cherry', 'date']
Initial list: ['apple', 'banana', 'cherry']
After append('date'): ['apple', 'banana', 'cherry', 'date']
# extend()
fruits.extend(["elderberry", "fig"])
print(f"After extend(['elderberry', 'fig']): {fruits}") # Output: ['apple', 'banana', 'cherry', 'date', 'elderberry', 'fig']
After extend(['elderberry', 'fig']): ['apple', 'banana', 'cherry', 'date', 'elderberry', 'fig']
# remove()
fruits.remove("banana")
print(f"After remove('banana'): {fruits}") # Output: ['apple', 'cherry', 'date', 'elderberry', 'fig']
After remove('banana'): ['apple', 'cherry', 'date', 'elderberry', 'fig']
# pop()
last_fruit = fruits.pop() # Removes 'fig'
first_fruit = fruits.pop(0) # Removes 'apple'
print(f"Popped last: {last_fruit}, Popped first: {first_fruit}, List: {fruits}")
Popped last: fig, Popped first: apple, List: ['cherry', 'date', 'elderberry']
# index() and count()
numbers = [1, 5, 8, 5, 2, 5]
print(f"Index of first '5': {numbers.index(5)}") # Output: 1
print(f"Count of '5': {numbers.count(5)}") # Output: 3
Index of first '5': 1
Count of '5': 3
# sort() and reverse()
numbers.sort()
print(f"After sort(): {numbers}") # Output: [1, 2, 5, 5, 5, 8]
numbers.reverse()
print(f"After reverse(): {numbers}") # Output: [8, 5, 5, 5, 2, 1]
After sort(): [1, 2, 5, 5, 5, 8]
After reverse(): [8, 5, 5, 5, 2, 1]
# copy() (Shallow vs. Deep)
# A shallow copy creates a new list, but references to nested objects are shared.
original = [[1, 2], 3, 4]
copied = original.copy()
copied[0][0] = 99 # This will change the original list too, as the inner list is shared!
print(f"\nOriginal after modifying copy: {original}") # Output: [[99, 2], 3, 4]
print(f"Copied list: {copied}") # Output: [[99, 2], 3, 4]
Original after modifying copy: [[99, 2], 3, 4]
Copied list: [[99, 2], 3, 4]

2.6.1.1.1. Using the del statement#

The del keyword is a statement, not a list method (which is why it’s not called like del()). It is a general-purpose statement in Python used to delete objects, including items from a list at a specified index or a slice.

  • Syntax: del list[index] or del list[start:end]

  • Behavior: It removes the item(s) from the list at the specified position(s) and does not return the removed item(s).

# del statement (removes by index)
print(f"\nUsing 'del' statement:")
colors = ['red', 'green', 'blue', 'yellow']
print(f"Original list: {colors}")
del colors[1] # Deletes the item at index 1 ('green')
print(f"After 'del colors[1]': {colors}") # Output: ['red', 'blue', 'yellow']
del colors[1:3] # Deletes a slice of items
print(f"After 'del colors[1:3]': {colors}") # Output: ['red']
Using 'del' statement:
Original list: ['red', 'green', 'blue', 'yellow']
After 'del colors[1]': ['red', 'blue', 'yellow']
After 'del colors[1:3]': ['red']

2.6.1.2. Using Lists as Stacks#

A Stack is a data structure that follows the LIFO (Last-In, First-Out) principle. The last item added is the first one to be removed. You can easily implement a stack using a list.

  • Pushing an item: Use append() to add an item to the top of the stack.

  • Popping an item: Use pop() to remove and return the item from the top of the stack.

# Create a stack of tasks
task_stack = []

# Push items onto the stack (append)
task_stack.append("Task 1: Prepare slides")
task_stack.append("Task 2: Write code")
task_stack.append("Task 3: Review documentation")

print(f"\nInitial stack: {task_stack}")
Initial stack: ['Task 1: Prepare slides', 'Task 2: Write code', 'Task 3: Review documentation']
# Pop items from the stack (the last one added is removed first)
last_task = task_stack.pop()
print(f"Popped task: '{last_task}'") # Output: 'Task 3: Review documentation'

current_task = task_stack.pop()
print(f"Popped task: '{current_task}'") # Output: 'Task 2: Write code'

print(f"Stack after popping: {task_stack}") # Output: ['Task 1: Prepare slides']
Popped task: 'Task 3: Review documentation'
Popped task: 'Task 2: Write code'
Stack after popping: ['Task 1: Prepare slides']

2.6.1.3. Using Lists as Queues#

A Queue is a data structure that follows the FIFO (First-In, First-Out) principle. The first item added is the first one to be removed.

  • Enqueuing (adding): Use append() to add an item to the end of the queue.

  • Dequeuing (removing): Use pop(0) to remove and return the item from the beginning of the queue.

Important Performance Note: For large lists, pop(0) is very inefficient. Removing the first element requires shifting all other elements one position to the left, which can be a slow operation (O(n) time complexity).

Recommendation: For a true and efficient queue, use collections.deque. It’s a double-ended queue designed for fast appends and pops from both ends.

from collections import deque

# Using deque for an efficient queue
customer_queue = deque(["Customer A", "Customer B", "Customer C"])

print(f"\nInitial queue: {customer_queue}")

# Enqueue an item (append to the right)
customer_queue.append("Customer D")
print(f"After adding 'Customer D': {customer_queue}")

# Dequeue an item (pop from the left)
next_customer = customer_queue.popleft()
print(f"Serving customer: '{next_customer}'") # Output: 'Customer A'

print(f"Queue after serving: {customer_queue}")
Initial queue: deque(['Customer A', 'Customer B', 'Customer C'])
After adding 'Customer D': deque(['Customer A', 'Customer B', 'Customer C', 'Customer D'])
Serving customer: 'Customer A'
Queue after serving: deque(['Customer B', 'Customer C', 'Customer D'])

2.6.1.4. List Comprehensions#

List comprehensions provide a concise and elegant way to create lists. They are a more readable and often faster alternative to using for loops with append().

  • Syntax: [expression for item in iterable if condition]

# Old way: Using a for loop
squares_old = []
for i in range(1, 6):
    squares_old.append(i**2)
print(f"\nSquares (old way): {squares_old}")

# New way: Using a list comprehension
squares_new = [i**2 for i in range(1, 6)]
print(f"Squares (new way): {squares_new}")

# List comprehension with a conditional (filtering)
even_squares = [i**2 for i in range(1, 11) if i % 2 == 0]
print(f"Even numbers squared: {even_squares}") # Output: [4, 16, 36, 64, 100]
Squares (old way): [1, 4, 9, 16, 25]
Squares (new way): [1, 4, 9, 16, 25]
Even numbers squared: [4, 16, 36, 64, 100]

2.6.1.5. Nested List Comprehensions#

You can also use list comprehensions to create nested lists, often replacing nested loops. This is useful for creating matrices or grids.

# Create a 3x3 matrix (list of lists)
matrix = [[j for j in range(3)] for i in range(3)]
print(f"\n3x3 matrix: {matrix}")
# Output: [[0, 1, 2], [0, 1, 2], [0, 1, 2]]

# Flatten a nested list into a single list
nested_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened_list = [num for sublist in nested_list for num in sublist]
print(f"Flattened list: {flattened_list}") # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
3x3 matrix: [[0, 1, 2], [0, 1, 2], [0, 1, 2]]
Flattened list: [1, 2, 3, 4, 5, 6, 7, 8, 9]

2.6.2. Tuples#

A tuple is an ordered, immutable collection of items.

  • Definition/Purpose: Often used for fixed collections of items where the content is not expected to change, such as coordinates, database records, or function arguments.

  • Key Characteristics:

    • Ordered: Items have a defined order, similar to lists.

    • Immutable: This is its key feature. Once a tuple is created, you cannot change its elements, add new ones, or remove existing ones.

    • Allows Duplicates: Can hold multiple items with the same value.

    • Indexed: Items can be accessed using an integer index.

    • Slicing: Supports slicing to extract sub-sequences.

    • Heterogeneous: Can contain items of different data types.

    • Fixed Size: Its size is determined at creation and cannot change.

  • Syntax: (item1, item2, item3, ...) For a single-item tuple, you must include a trailing comma: (item,)

# Creating a tuple
empty_tuple = ()
my_tuple = ("red", "green", "blue", 123)
single_item_tuple = (42,) # Note the comma for single item tuple
another_tuple = "apple", "banana" # Parentheses are often optional during creation

print(f"Empty tuple: {empty_tuple}")
print(f"My tuple: {my_tuple}")
print(f"Single item tuple: {single_item_tuple}")
print(f"Another tuple: {another_tuple}")
Empty tuple: ()
My tuple: ('red', 'green', 'blue', 123)
Single item tuple: (42,)
Another tuple: ('apple', 'banana')
# Accessing elements
print(f"\nAccessing Elements:")
print(f"First element (my_tuple[0]): {my_tuple[0]}") # Output: red
print(f"Last element (my_tuple[-1]): {my_tuple[-1]}") # Output: 123
Accessing Elements:
First element (my_tuple[0]): red
Last element (my_tuple[-1]): 123
# --- Attempting to modify (will cause a TypeError!) ---
print(f"\nAttempting to Modify:")
try:
    my_tuple[0] = "yellow"
except TypeError as e:
    print(f"Error! Tuples are immutable: {e}")
Attempting to Modify:
Error! Tuples are immutable: 'tuple' object does not support item assignment
# Slicing tuples (returns a new tuple)
print(f"\nSlicing Tuples:")
print(f"Sliced tuple (my_tuple[1:3]): {my_tuple[1:3]}") # Output: ('green', 'blue')
Slicing Tuples:
Sliced tuple (my_tuple[1:3]): ('green', 'blue')
# Tuple packing and unpacking
print(f"\nTuple Packing and Unpacking:")
coordinates = (10, 20) # Packing
x, y = coordinates     # Unpacking
print(f"Coordinates: x={x}, y={y}")
Tuple Packing and Unpacking:
Coordinates: x=10, y=20
# Functions returning multiple values often return them as a tuple
def get_min_max(numbers):
    return min(numbers), max(numbers)

min_val, max_val = get_min_max([10, 5, 20, 1])
print(f"Min: {min_val}, Max: {max_val}")
Min: 1, Max: 20

2.6.3. Sets#

A set is an unordered collection of unique items.

  • Definition/Purpose: Primarily used for membership testing, removing duplicates from a sequence, and mathematical set operations (union, intersection, difference).

  • Key Characteristics:

    • Unordered: Items do not have a defined order and cannot be accessed by index or key. The order of elements when printed might vary.

    • Mutable: You can add or remove items from a set, but you cannot change individual elements in place (as elements are not indexed).

    • No Duplicates: Automatically removes duplicate elements. If you add an existing element, the set remains unchanged.

    • Unindexed: Does not support indexing or slicing.

    • Elements Must Be Hashable: Elements must be immutable and hashable (like numbers, strings, tuples containing only immutable types). Lists or dictionaries cannot be direct elements of a set.

    • Dynamic Size: Can grow or shrink.

  • Syntax: {item1, item2, ...} (use set() to create an empty set, as {} creates an empty dictionary).

# Creating a set with duplicate values
my_set = {"apple", "banana", "cherry", "apple", "banana"}
print(f"Set with duplicates removed: {my_set}") # Output might be {'cherry', 'banana', 'apple'} (order is not guaranteed)
Set with duplicates removed: {'apple', 'banana', 'cherry'}
# Creating an empty set
empty_set = set()
print(f"Empty set: {empty_set}")
Empty set: set()
# Adding elements
print(f"\nAdding Elements:")
my_set.add("orange")
my_set.add("cherry") # Adding an existing element has no effect
print(f"Set after adding 'orange' and 'cherry': {my_set}")
Adding Elements:
Set after adding 'orange' and 'cherry': {'apple', 'banana', 'orange', 'cherry'}
# Removing elements
print(f"\nRemoving Elements:")
my_set.remove("banana") # Removes a specific element
print(f"Set after removing 'banana': {my_set}")
my_set.discard("grape") # Removes 'grape' if it exists, but does not raise an error if not found
print(f"Set after discarding 'grape': {my_set}")
popped_item = my_set.pop() # Removes and returns an arbitrary element
print(f"Popped item: {popped_item}, Set: {my_set}") # Note: The item removed is arbitrary due to unordered nature
Removing Elements:
Set after removing 'banana': {'apple', 'orange', 'cherry'}
Set after discarding 'grape': {'apple', 'orange', 'cherry'}
Popped item: apple, Set: {'orange', 'cherry'}
# Set operations (mathematical operations)
print(f"\nSet Operations:")
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
Set Operations:
print(f"Set1: {set1}, Set2: {set2}")
print(f"Union (set1 | set2): {set1 | set2}") # All unique elements from both sets
print(f"Intersection (set1 & set2): {set1 & set2}") # Common elements
print(f"Difference (set1 - set2): {set1 - set2}") # Elements in set1 but not in set2
print(f"Symmetric Difference (set1 ^ set2): {set1 ^ set2}") # Elements in either set, but not in both
Set1: {1, 2, 3, 4}, Set2: {3, 4, 5, 6}
Union (set1 | set2): {1, 2, 3, 4, 5, 6}
Intersection (set1 & set2): {3, 4}
Difference (set1 - set2): {1, 2}
Symmetric Difference (set1 ^ set2): {1, 2, 5, 6}
# Checking for membership (very fast!)
print(f"\nMembership Testing:")
print(f"Is 'apple' in my_set? {'apple' in my_set}") # Output: True
print(f"Is 'banana' in my_set? {'banana' in my_set}") # Output: False (it was removed)
Membership Testing:
Is 'apple' in my_set? False
Is 'banana' in my_set? False
# Converting list to set to remove duplicates
numbers_with_duplicates = [1, 2, 2, 3, 4, 4, 5]
unique_numbers = list(set(numbers_with_duplicates))
print(f"\nOriginal list with duplicates: {numbers_with_duplicates}")
print(f"List after removing duplicates: {unique_numbers}")
Original list with duplicates: [1, 2, 2, 3, 4, 4, 5]
List after removing duplicates: [1, 2, 3, 4, 5]

2.6.3.1. frozenset#

A frozenset is an immutable version of a set. Once created, you cannot add or remove elements. This makes frozensets hashable, meaning they can be used as elements in other sets or as keys in dictionaries.

# Creating a frozenset
immutable_set = frozenset([1, 2, 3, 2])
print(f"Frozenset: {immutable_set}")
print(f"Type of frozenset: {type(immutable_set)}")

# Attempting to add/remove (will raise an AttributeError)
try:
    immutable_set.add(4)
except AttributeError as e:
    print(f"Error! Frozensets are immutable: {e}")

# Frozensets can be elements of a regular set
nested_set = {frozenset([1, 2]), frozenset([3, 4])}
print(f"Nested set with frozensets: {nested_set}")
Frozenset: frozenset({1, 2, 3})
Type of frozenset: <class 'frozenset'>
Error! Frozensets are immutable: 'frozenset' object has no attribute 'add'
Nested set with frozensets: {frozenset({3, 4}), frozenset({1, 2})}

2.6.4. Dictionaries#

A dict (dictionary) is a collection of key-value pairs. Each unique key maps to a specific value.

  • Definition/Purpose: Ideal for storing data where each piece of information is associated with a unique identifier (the key), allowing for very fast lookups.

  • Key Characteristics:

    • Ordered (since Python 3.7): Items are kept in the order they were inserted. Before 3.7, they were unordered.

    • Mutable: You can add new key-value pairs, remove existing ones, and change the values associated with keys.

    • Keys are Unique: Each key must be unique within a dictionary. If you assign a new value to an existing key, it overwrites the old value.

    • Keys Must Be Immutable and Hashable: Keys must be of an immutable type (e.g., strings, numbers, tuples containing only immutable types). Lists and dictionaries cannot be keys.

    • Values Can Be Anything: Values can be of any data type and can be duplicates.

    • Mapped: You access values using their associated keys, not a numerical index.

    • Dynamic Size: Can grow or shrink as needed.

  • Syntax: {key1: value1, key2: value2, ...}

# Creating a dictionary
empty_dict = {}
person = {
    "name": "Alice",
    "age": 30,
    "city": "New York",
    "occupation": "Engineer",
    "skills": ["Python", "SQL", "ML"] # Value can be a list
}

print(f"Empty dictionary: {empty_dict}")
print(f"Person dictionary: {person}")
Empty dictionary: {}
Person dictionary: {'name': 'Alice', 'age': 30, 'city': 'New York', 'occupation': 'Engineer', 'skills': ['Python', 'SQL', 'ML']}
# Accessing values by key
print(f"\nAccessing Values:")
print(f"Person's name (person['name']): {person['name']}") # Output: Alice
print(f"Person's age (person.get('age')): {person.get('age')}") # Safer way to access (returns None if key not found)
print(f"Person's country (person.get('country', 'Not specified')): {person.get('country', 'Not specified')}") # Provides a default value
Accessing Values:
Person's name (person['name']): Alice
Person's age (person.get('age')): 30
Person's country (person.get('country', 'Not specified')): Not specified
# Modifying a value
print(f"\nModifying Values:")
person["age"] = 31
print(f"Dictionary after modifying age: {person}")
Modifying Values:
Dictionary after modifying age: {'name': 'Alice', 'age': 31, 'city': 'New York', 'occupation': 'Engineer', 'skills': ['Python', 'SQL', 'ML']}
# Adding a new key-value pair
print(f"\nAdding Key-Value Pairs:")
person["email"] = "alice@example.com"
print(f"Dictionary after adding email: {person}")
Adding Key-Value Pairs:
Dictionary after adding email: {'name': 'Alice', 'age': 31, 'city': 'New York', 'occupation': 'Engineer', 'skills': ['Python', 'SQL', 'ML'], 'email': 'alice@example.com'}
# Removing key-value pairs
print(f"\nRemoving Key-Value Pairs:")
removed_occupation = person.pop("occupation") # Removes key and returns its value
print(f"Removed occupation: {removed_occupation}, Dictionary: {person}")
del person["city"] # Deletes key-value pair
print(f"Dictionary after deleting city: {person}")
Removing Key-Value Pairs:
Removed occupation: Engineer, Dictionary: {'name': 'Alice', 'age': 31, 'city': 'New York', 'skills': ['Python', 'SQL', 'ML'], 'email': 'alice@example.com'}
Dictionary after deleting city: {'name': 'Alice', 'age': 31, 'skills': ['Python', 'SQL', 'ML'], 'email': 'alice@example.com'}
# Iterating through a dictionary
print(f"\nIterating Through Dictionary:")
print(f"Keys:")
for key in person: # Iterates over keys by default
    print(key)
print(f"Values:")
for value in person.values(): # Iterates over values
    print(value)
print(f"Items (key-value pairs):")
for key, value in person.items(): # Iterates over (key, value) tuples
    print(f"{key}: {value}")
Iterating Through Dictionary:
Keys:
name
age
skills
email
Values:
Alice
31
['Python', 'SQL', 'ML']
alice@example.com
Items (key-value pairs):
name: Alice
age: 31
skills: ['Python', 'SQL', 'ML']
email: alice@example.com
# Checking for key existence
print(f"\nChecking Key Existence:")
print(f"Is 'name' in person? {'name' in person}") # Output: True
print(f"Is 'salary' in person? {'salary' in person}") # Output: False
Checking Key Existence:
Is 'name' in person? True
Is 'salary' in person? False

2.6.5. Arrays (from array module) and Numeric Data#

While lists are incredibly flexible, they can store heterogeneous data. When you need to store a large sequence of items of the exact same primitive data type (like all integers or all floats) and care about memory efficiency, Python’s built-in array module can be used.

  • Definition/Purpose: Provides a space-efficient way to store arrays of basic numeric types. Less flexible than lists but more memory-efficient for homogeneous numerical data.

  • Key Characteristics:

    • Homogeneous: All elements must be of the same specified type (e.g., all signed integers, all floating-point numbers). This type is specified by a ‘type code’ during creation.

    • Mutable: Elements can be changed after creation.

    • Ordered & Indexed: Like lists, elements maintain order and are accessed by index.

    • Memory Efficient: Stores elements more compactly than a Python list, which stores full Python objects.

  • Syntax: from array import array array(typecode, [initial items])

    Common typecode examples:

    • 'i': signed integer (2 bytes)

    • 'f': float (4 bytes)

    • 'd': double (8 bytes)

Important Note: For serious numerical computing and data science, the NumPy ndarray (from the numpy library) is the de facto standard. It provides highly optimized array operations and a vast ecosystem of scientific functions, far beyond the basic array module.

from array import array

# Creating an array of signed integers ('i' typecode)
my_int_array = array('i', [10, 20, 30, 40, 50])
print(f"Integer array: {my_int_array}")
print(f"Type of the array: {type(my_int_array)}")
Integer array: array('i', [10, 20, 30, 40, 50])
Type of the array: <class 'array.array'>
# Creating an array of double-precision floats ('d' typecode)
my_float_array = array('d', [1.1, 2.2, 3.3])
print(f"Float array: {my_float_array}")
Float array: array('d', [1.1, 2.2, 3.3])
# Accessing and modifying elements works like a list
print(f"Third element (my_int_array[2]): {my_int_array[2]}") # Output: 30
my_int_array[0] = 5
print(f"Array after modification: {my_int_array}")
Third element (my_int_array[2]): 30
Array after modification: array('i', [5, 20, 30, 40, 50])
# Attempting to add a different data type (will cause a TypeError!)
try:
    my_int_array.append(3.14) # Trying to add a float to an integer array
except TypeError as e:
    print(f"Error! Array requires a homogeneous type: {e}")
Error! Array requires a homogeneous type: 'float' object cannot be interpreted as an integer
# Attempting to add the wrong integer type (e.g., too large for 'i')
try:
    my_int_array.append(2**31) # Value too large for typical 'i' (signed 32-bit int)
except OverflowError as e:
    print(f"Error! Value too large for array type: {e}")
Error! Value too large for array type: signed integer is greater than maximum

2.6.6. Summary of Python’s Core Data Structures#

Data Structure

Type

Mutability

Ordered

Allows Duplicates

Accessed By

Common Use Cases

List

Sequence

Mutable

✅ Yes

✅ Yes

Index (e.g., [0])

General-purpose collection, dynamic arrays, ordered items.

Tuple

Sequence

Immutable

✅ Yes

✅ Yes

Index (e.g., [0])

Fixed collections, function arguments/returns, dictionary keys.

Set

Unordered Collection

Mutable

❌ No

❌ No

N/A (no index)

Membership testing, removing duplicates, mathematical set ops.

Frozenset

Unordered Collection

Immutable

❌ No

❌ No

N/A (no index)

Immutable sets, elements of other sets, dictionary keys.

Dictionary

Mapping

Mutable

✅ Yes

❌ No (keys), ✅ Yes (values)

Key (e.g., ['key'])

Key-value storage, lookup tables, representing structured data.