0%
Python Data Structures: Lists, Tuples, Dictionaries, and Sets

Data Structures

Organize and manipulate your data efficiently

10-15 min

Python Data Structures: Lists, Tuples, Dictionaries, and Sets

Data structures are specialized formats for organizing, storing, and manipulating information. Python has several built-in data structures that make it easier to manage collections of items. In this tutorial, we will explore Python’s four main data structures: lists, tuples, dictionaries, and sets.

Understanding these structures and knowing when to use each one is essential for writing efficient, elegant Python code.

Lists

Lists are one of the most versatile and frequently used data structures in Python. A list is an ordered, mutable collection of items.

Creating Lists

# Empty list
ma_liste = []

# List with items
nombres = [1, 2, 3, 4, 5]
fruits = ["pomme", "banane", "orange"]
mixte = [1, "hello", 3.14, True]  # Can contain different types

# Creating with the list() function
caracteres = list("Python")  # ['P', 'y', 't', 'h', 'o', 'n']

Accessing List Items

List items are indexed starting at 0.

fruits = ["pomme", "banane", "orange", "fraise", "kiwi"]

# Access by positive index (from the start)
premier_fruit = fruits[0]  # "pomme"
troisieme_fruit = fruits[2]  # "orange"

# Access by negative index (from the end)
dernier_fruit = fruits[-1]  # "kiwi"
avant_dernier = fruits[-2]  # "fraise"

# Slicing
deux_premiers = fruits[0:2]  # ["pomme", "banane"]
du_deuxieme_au_quatrieme = fruits[1:4]  # ["banane", "orange", "fraise"]
tous_sauf_premier = fruits[1:]  # ["banane", "orange", "fraise", "kiwi"]
trois_premiers = fruits[:3]  # ["pomme", "banane", "orange"]

Modifying Lists

fruits = ["pomme", "banane", "orange"]

# Modifying an item
fruits[1] = "poire"  # ["pomme", "poire", "orange"]

# Adding items
fruits.append("fraise")  # Adds to the end: ["pomme", "poire", "orange", "fraise"]
fruits.insert(1, "kiwi")  # Inserts at index 1: ["pomme", "kiwi", "poire", "orange", "fraise"]
fruits.extend(["cerise", "raisin"])  # Adds multiple items: ["pomme", "kiwi", "poire", "orange", "fraise", "cerise", "raisin"]

# Removing items
fruits.remove("poire")  # Removes the first occurrence of "poire"
fruit_supprime = fruits.pop(2)  # Removes and returns the item at index 2
del fruits[0]  # Removes the item at index 0
fruits.clear()  # Empties the list completely

Common List Operations

nombres = [3, 1, 4, 1, 5, 9, 2, 6]

# Length
longueur = len(nombres)  # 8

# Minimum and maximum
minimum = min(nombres)  # 1
maximum = max(nombres)  # 9

# Sum
total = sum(nombres)  # 31

# Sorting
nombres.sort()  # Sorts the list in place: [1, 1, 2, 3, 4, 5, 6, 9]
nombres_decroissants = sorted(nombres, reverse=True)  # Returns a new sorted list: [9, 6, 5, 4, 3, 2, 1, 1]

# Reversing
nombres.reverse()  # Reverses the list in place

# Counting
occurrences_de_1 = nombres.count(1)  # 2

# Finding an index
premier_index_de_1 = nombres.index(1)  # Index of the first occurrence of 1

List Comprehensions

A list comprehension is a concise syntax for creating lists based on existing sequences.

# Without a list comprehension
carres = []
for x in range(10):
    carres.append(x**2)

# With a list comprehension
carres = [x**2 for x in range(10)]  # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# With a condition
nombres_pairs = [x for x in range(20) if x % 2 == 0]  # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

# More complex list comprehension
matrice = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
aplati = [nombre for ligne in matrice for nombre in ligne]  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

Common Use Cases for Lists

Lists are ideal when you need to:

  • Store a collection of items in a specific order
  • Frequently modify the collection (add/remove items)
  • Access items by their position
  • Store duplicates

Example applications:

  • To-do lists
  • Action history
  • Processing queues
  • Time series data

Tuples

Tuples are similar to lists, but they are immutable (they cannot be modified after creation). This makes them more efficient in terms of memory and performance.

Creating Tuples

# Empty tuple
mon_tuple = ()

# Tuple with a single item (requires a comma)
singleton = (42,)

# Tuple with multiple items
coordonnees = (10, 20)
personne = ("Alice", 30, "Ingénieure")

# Creation without parentheses (packing)
autre_tuple = 1, 2, 3

# Creating with the tuple() function
caracteres = tuple("Python")  # ('P', 'y', 't', 'h', 'o', 'n')

Accessing Tuple Items

Accessing tuple items works the same way as for lists:

coordonnees = (10, 20, 30, 40, 50)

x = coordonnees[0]  # 10
y = coordonnees[1]  # 20
dernier = coordonnees[-1]  # 50
sous_tuple = coordonnees[1:4]  # (20, 30, 40)

Tuple Unpacking

A particularly useful feature of tuples is unpacking:

# Basic unpacking
point = (10, 20)
x, y = point  # x = 10, y = 20

# Unpacking with * to capture multiple items
premier, *milieu, dernier = (1, 2, 3, 4, 5)
# premier = 1, milieu = [2, 3, 4], dernier = 5

# Swapping variables
a, b = 1, 2
a, b = b, a  # a = 2, b = 1

Tuple Operations

coordonnees = (10, 20, 30)
plus_dimensions = coordonnees + (40, 50)  # Concatenation: (10, 20, 30, 40, 50)
repete = coordonnees * 2  # Repetition: (10, 20, 30, 10, 20, 30)

Tuple Methods

Because tuples are immutable, they have fewer methods than lists:

nombres = (3, 1, 4, 1, 5, 9, 2)

longueur = len(nombres)  # 7
occurrences_de_1 = nombres.count(1)  # 2
premier_index_de_1 = nombres.index(1)  # 1

Common Use Cases for Tuples

Tuples are preferable when:

  • You have a collection of items that will not change
  • You want to prevent accidental modification of data
  • You need a hashable key for dictionaries (lists cannot be keys)
  • You are returning multiple values from a function

Example applications:

  • Coordinates (x, y, z)
  • Database records
  • Composite keys
  • RGB color values
  • Fixed configuration parameters

Dictionaries

Dictionaries are unordered collections of key-value pairs. They allow fast access, insertion, and deletion of items by key.

Creating Dictionaries

# Empty dictionary
mon_dict = {}

# Dictionary with key-value pairs
etudiant = {
    "nom": "Martin",
    "age": 22,
    "cours": ["Mathématiques", "Informatique", "Physique"]
}

# Creating with the dict() function
coordonnees = dict(x=10, y=20, z=30)

# Creating from key-value tuples
items = [("pomme", 3), ("banane", 5), ("orange", 2)]
inventaire = dict(items)

# Dictionary comprehension
carre_des_nombres = {x: x**2 for x in range(6)}  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

Accessing and Modifying Values

etudiant = {
    "nom": "Martin",
    "age": 22,
    "cours": ["Mathématiques", "Informatique", "Physique"]
}

# Accessing values
nom = etudiant["nom"]  # "Martin"

# Modifying values
etudiant["age"] = 23
etudiant["cours"].append("Chimie")

# Safe access with get()
telephone = etudiant.get("telephone")  # None because the key does not exist
telephone = etudiant.get("telephone", "Non spécifié")  # Default value "Non spécifié"

# Adding a new key-value pair
etudiant["email"] = "martin@example.com"

# Removal
cours = etudiant.pop("cours")  # Removes and returns the value
del etudiant["age"]  # Removes the key-value pair
etudiant.clear()  # Empties the dictionary

Dictionary Methods

produits = {
    "pomme": 0.80,
    "banane": 0.50,
    "orange": 0.70
}

# Getting keys, values, and items
cles = produits.keys()  # dict_keys(['pomme', 'banane', 'orange'])
valeurs = produits.values()  # dict_values([0.8, 0.5, 0.7])
items = produits.items()  # dict_items([('pomme', 0.8), ('banane', 0.5), ('orange', 0.7)])

# Updating the dictionary
nouveaux_produits = {"kiwi": 1.20, "banane": 0.55}
produits.update(nouveaux_produits)  # Adds "kiwi" and changes the price of "banane"

# Checking whether a key exists
if "pomme" in produits:
    print("Le prix des pommes est", produits["pomme"])

Iterating Over Dictionaries

produits = {
    "pomme": 0.80,
    "banane": 0.50,
    "orange": 0.70
}

# Iterating over keys (default behavior)
for produit in produits:
    print(produit)

# Iterating over values
for prix in produits.values():
    print(prix)

# Iterating over key-value pairs
for produit, prix in produits.items():
    print(f"{produit}: {prix}€")

Nested Dictionaries

# Dictionary of dictionaries
etudiants = {
    "101": {
        "nom": "Alice",
        "age": 22,
        "cours": ["Math", "Informatique"]
    },
    "102": {
        "nom": "Bob",
        "age": 21,
        "cours": ["Physique", "Biologie"]
    }
}

# Accessing nested items
nom_etudiant_102 = etudiants["102"]["nom"]  # "Bob"
cours_alice = etudiants["101"]["cours"][1]  # "Informatique"

Common Use Cases for Dictionaries

Dictionaries are perfect for:

  • Collections where you look up items by a unique key
  • Storing associative data (key-value pairs)
  • Fast hash tables for lookups
  • Representing objects with attributes

Example applications:

  • Data caching
  • Frequency counting
  • In-memory databases
  • Configuration parameters
  • Storing JSON object properties

Sets

Sets are unordered collections of unique items. They are optimized for membership testing and eliminating duplicates.

Creating Sets

# Empty set (you cannot use {}, because that creates an empty dictionary)
mon_ensemble = set()

# Set with items
couleurs = {"rouge", "vert", "bleu"}

# Creating from a list (removes duplicates)
nombres = set([1, 2, 2, 3, 4, 4, 5])  # {1, 2, 3, 4, 5}

# Set comprehension
nombres_pairs = {x for x in range(10) if x % 2 == 0}  # {0, 2, 4, 6, 8}

Set Operations

fruits1 = {"pomme", "banane", "orange"}
fruits2 = {"orange", "kiwi", "ananas"}

# Adding and removing items
fruits1.add("fraise")
fruits1.remove("banane")  # Raises an error if the item does not exist
fruits1.discard("cerise")  # Does not raise an error if the item does not exist
element = fruits1.pop()  # Removes and returns an arbitrary item
fruits1.clear()  # Empties the set

# Membership testing
"orange" in fruits2  # True
"banane" in fruits2  # False

Mathematical Set Operations

Sets let you perform mathematical set operations:

A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7, 8}

# Union (items that are in A OR in B)
union = A | B  # {1, 2, 3, 4, 5, 6, 7, 8}
# or
union = A.union(B)

# Intersection (items that are in A AND in B)
intersection = A & B  # {4, 5}
# or
intersection = A.intersection(B)

# Difference (items that are in A but NOT in B)
difference = A - B  # {1, 2, 3}
# or
difference = A.difference(B)

# Symmetric difference (items that are in A OR B but NOT in both)
diff_sym = A ^ B  # {1, 2, 3, 6, 7, 8}
# or
diff_sym = A.symmetric_difference(B)

# Check whether a set is a subset of another
est_sous_ensemble = {1, 2}.issubset(A)  # True

# Check whether a set is a superset of another
est_sur_ensemble = A.issuperset({1, 2})  # True

Frozen Sets (frozenset)

A frozenset is an immutable set, which makes it hashable. It can be used as a dictionary key.

# Creating a frozenset
couleurs_primaires = frozenset(["rouge", "bleu", "jaune"])

# Using it as a dictionary key
groupes_couleurs = {
    frozenset(["rouge", "bleu", "jaune"]): "primaires",
    frozenset(["violet", "orange", "vert"]): "secondaires"
}

Common Use Cases for Sets

Sets are particularly useful for:

  • Removing duplicates from a collection
  • Quickly testing item membership
  • Performing mathematical operations on sets
  • Finding unique or common items across collections

Example applications:

  • Duplicate filtering
  • Unique data analysis
  • Operations on sets of tags
  • User permission management
  • Graph representation (set theory)

Choosing the Right Data Structure

Each data structure has its strengths and weaknesses. Here is a comparison table to help you choose the one best suited to your needs:

StructureOrderedMutableIndexed itemsKey-value itemsUnique itemsTypical use case
List✅✅✅❌❌Mutable ordered collection
Tuple✅❌✅❌❌Immutable ordered collection
Dictionary❌¹✅❌✅✅ (keys)Key-value mapping
Set❌✅❌❌✅Duplicate removal, set operations

¹ Since Python 3.7, dictionaries preserve insertion order, but they are not considered inherently ordered in their design.

Tips for Choosing the Right Structure

  • Use a list when order and modification matter
  • Use a tuple for read-only data or composite keys
  • Use a dictionary for fast lookups by key
  • Use a set when uniqueness and set operations are needed

Practical Example: Data Analysis

Here is an example that uses all of the data structures to analyze sales:

# Sales data (product, quantity, price, category)
ventes = [
    ("pomme", 5, 0.80, "fruits"),
    ("banane", 3, 0.50, "fruits"),
    ("pain", 2, 1.20, "boulangerie"),
    ("lait", 1, 0.90, "laitier"),
    ("fromage", 2, 2.50, "laitier"),
    ("pomme", 2, 0.80, "fruits"),
    ("yaourt", 4, 0.60, "laitier")
]

# Using dictionaries to group by category
ventes_par_categorie = {}
for produit, quantite, prix, categorie in ventes:
    if categorie not in ventes_par_categorie:
        ventes_par_categorie[categorie] = []
    ventes_par_categorie[categorie].append((produit, quantite, prix))

# Using sets to get unique products
produits_uniques = {produit for produit, _, _, _ in ventes}
print(f"Produits uniques: {produits_uniques}")  # {'pain', 'pomme', 'fromage', 'lait', 'yaourt', 'banane'}

# Using tuples to store totals per category (immutable)
totaux_par_categorie = tuple(
    (categorie, sum(quantite * prix for _, quantite, prix in produits))
    for categorie, produits in ventes_par_categorie.items()
)
print("Totaux par catégorie:", totaux_par_categorie)

# Using lists and comprehensions to analyze sales
total_par_produit = {}
for produit, quantite, prix, _ in ventes:
    if produit in total_par_produit:
        total_par_produit[produit] = (total_par_produit[produit][0] + quantite, prix)
    else:
        total_par_produit[produit] = (quantite, prix)

# List of products sorted by revenue
ca_par_produit = [(produit, quantite * prix) for produit, (quantite, prix) in total_par_produit.items()]
ca_par_produit.sort(key=lambda x: x[1], reverse=True)

print("Produits par chiffre d'affaires:")
for produit, ca in ca_par_produit:
    print(f"- {produit}: {ca:.2f}€")

Data Structure Performance

For applications that handle large amounts of data, performance becomes important. Here is an overview of the time complexities for common operations:

OperationListTupleDictionarySet
Access by indexO(1)O(1)N/AN/A
Access by keyN/AN/AO(1)¹N/A
Element lookupO(n)O(n)O(1)¹O(1)¹
Adding an elementO(1)²N/AO(1)¹O(1)¹
Removing an elementO(n)N/AO(1)¹O(1)¹

¹ On average. In the worst case (rare), these operations can be O(n).
² Appending to the end is amortized O(1). Inserting in the middle is O(n).

Conclusion

Data structures are the fundamental building blocks of every Python program. Understanding their characteristics, advantages, and drawbacks will help you write more efficient and more elegant code.

To recap:

  • Lists are versatile and mutable, perfect for ordered collections
  • Tuples are immutable and efficient, ideal for read-only data
  • Dictionaries offer fast lookups by key, perfect for associations
  • Sets eliminate duplicates and enable efficient set operations

Choosing the right structure for each situation is a skill that improves with practice. Don’t hesitate to experiment with different approaches to solve your data problems.

In the next tutorial, we will explore functions in Python and how to use them to organize and reuse your code effectively.


Need to review the basics of Python? Check out our tutorial on the basics of Python syntax.

Comments

Comments are powered by GitHub Discussions

Sign in with GitHub to join the discussion

Link copied!