Les Dictionnaires en Python

1. Introduction aux Dictionnaires

Les dictionnaires sont une structure de données essentielle en Python, permettant de stocker des paires clé-valeur. Cette structure est particulièrement puissante pour la gestion de données associatives, car elle offre un accès rapide aux valeurs via leurs clés. Les dictionnaires sont largement utilisés dans le développement Python pour leur flexibilité et leur efficacité.

1.1 Définition

Un dictionnaire en Python est une collection de paires clé-valeur. Avant Python 3.7, cette collection était non ordonnée, mais depuis, l'ordre d'insertion est préservé. Chaque clé dans un dictionnaire doit être unique et hashable, ce qui signifie qu'elle doit être de type immuable comme une chaîne de caractères, un nombre ou un tuple. Les valeurs, en revanche, peuvent être de n'importe quel type, y compris des listes ou d'autres dictionnaires.

1.2 Contextualisation

Les dictionnaires se distinguent des autres structures de données Python par leur capacité à associer des clés uniques à des valeurs. Voici une comparaison avec d'autres structures :

  • Listes : Collections ordonnées d'éléments accessibles par index, idéales pour les séquences de données.
  • Tuples : Collections ordonnées et immuables, souvent utilisées pour regrouper des données hétérogènes.
  • Sets : Collections non ordonnées d'éléments uniques, utiles pour les opérations d'ensemble comme l'union et l'intersection.
  • Dictionnaires : Collections de paires clé-valeur, optimisées pour l'accès rapide aux données par clé.

1.3 Cas d'utilisation

Les dictionnaires sont particulièrement adaptés pour :

  • Stockage de données associatives, comme des configurations d'application où chaque paramètre est identifié par un nom unique.
  • Cache de données avec accès rapide, permettant de stocker temporairement des résultats de calculs coûteux.
  • Représentation de structures de données complexes, telles que des objets JSON ou des arbres de données.
  • Mapping de valeurs, par exemple pour des traductions de texte ou des conversions d'unités.

1.4 Exemple de Code


# Example of creating a dictionary
person = {
"name": "Jean",
"age": 28,
"city": "Marseille"
}

# Accessing a value by key
print(person["name"])  # Output: Jean

# Adding a new key-value pair
person["profession"] = "Engineer"

# Updating an existing value
person["age"] = 29

# Deleting a key-value pair
del person["city"]

2. Caractéristiques Fondamentales

2.1 Propriétés de mutabilité

Les dictionnaires en Python sont :

  • Mutables : Peuvent être modifiés après création, ce qui signifie que vous pouvez ajouter, supprimer ou modifier des paires clé-valeur après la création du dictionnaire.
  • Dynamiques : Taille variable, permettant aux dictionnaires de croître ou de rétrécir selon les besoins.
  • Hashables : Les clés doivent être hashables, c'est-à-dire immuables, comme les str, int, ou tuple.

2.2 Ordonnancement

Depuis Python 3.7 :

  • Ordre d'insertion préservé, ce qui signifie que les éléments sont itérés dans l'ordre où ils ont été ajoutés.
  • Itération dans l'ordre d'insertion, facilitant la préservation de l'ordre logique des données.
  • Pas d'indexation numérique, les éléments sont accessibles uniquement par leurs clés.

2.3 Indexation et accès

Méthodes d'accès aux éléments :

  • Accès direct par clé : dict[key], qui lève une exception si la clé n'existe pas.
  • Méthode get() : dict.get(key, default), qui retourne une valeur par défaut si la clé n'existe pas.
  • Test d'existence : key in dict, pour vérifier si une clé est présente dans le dictionnaire.

2.4 Exemple de Code


# Example of a dictionary
inventory = {
"apples": 10,
"bananas": 5,
"oranges": 8
}

# Direct access by key
print(inventory["apples"])  # Output: 10

# Using get() to avoid errors
print(inventory.get("cherries", 0))  # Output: 0

# Checking for the existence of a key
if "bananas" in inventory:
print("Bananas available")

3. Création et Constructeur

3.1 Syntaxe de création


# Creation with curly braces
dict1 = {"name": "Python", "version": 3.9}

# Using the dict() constructor
dict2 = dict(name="Python", version=3.9)

# From a list of tuples
dict3 = dict([("name", "Python"), ("version", 3.9)])

# Dict comprehension
dict4 = {x: x**2 for x in range(5)}

3.2 Conversion


# Conversion from a list of tuples
tuple_list = [("a", 1), ("b", 2)]
dict_from_tuples = dict(tuple_list)

# Conversion from two lists
keys = ["x", "y", "z"]
values = [1, 2, 3]
dict_from_lists = dict(zip(keys, values))

4. Opérations de Base

4.1 Accès aux éléments


# Creating a dict example
user = {
"name": "Alice",
"age": 30,
"city": "Paris"
}

# Direct access
name = user["name"]  # "Alice"

# Safe access with get()
age = user.get("age", 0)  # 30
email = user.get("email", "Not specified")  # "Not specified"

# Existence check
if "city" in user:
print("City found:", user["city"])

4.2 Méthodes de modification


# Addition and update
user["email"] = "alice@example.com"  # Addition
user["age"] = 31  # Update

# Multiple updates with update()
user.update({
"phone": "0123456789",
"city": "Lyon"
})

# Deletion
del user["phone"]  # Deletion of a key
email = user.pop("email", None)  # Deletion with return value
last = user.popitem()  # Removes and returns the last pair

# Clearing
user.clear()  # Empties the dictionary

4.3 Opérations de tri


# Sorting keys
product = {
"name": "Computer",
"price": 1200,
"brand": "TechPro",
"available": True
}

# Get sorted keys
sorted_keys = sorted(product.keys())
print(sorted_keys)  # ['available', 'brand', 'name', 'price']

# Create an ordered dictionary by keys
from collections import OrderedDict
sorted_product = OrderedDict(sorted(product.items()))

# Sort by values (example with a dictionary of scores)
scores = {"Alice": 85, "Bob": 92, "Charlie": 78, "David": 95}
sorted_scores = dict(sorted(scores.items(), key=lambda item: item[1], reverse=True))
print(sorted_scores)  # {'David': 95, 'Bob': 92, 'Alice': 85, 'Charlie': 78}

5. Méthodes et Fonctions Spécifiques

5.1 Méthodes de base


# Example of creating a dictionary
config = {
"language": "Python",
"version": 3.11,
"environment": "production"
}

# Access to keys, values, and items
keys = config.keys()
values = config.values()
items = config.items()

# Update with update()
config.update({"version": 3.12, "cache_size": 256})

# Method setdefault()
value = config.setdefault("debug", False)

5.2 Compréhensions de dictionnaires


# Data transformation
student_grades = {"Alice": 85, "Bob": 92, "Charlie": 78}
adjusted_grades = {k: v+2 if v < 90 else v for k, v in student_grades.items()}

# Filtering with condition
grades_above_80 = {k: v for k, v in student_grades.items() if v > 80}

# Key-value inversion (if values are unique)
inversion = {v: k for k, v in student_grades.items()}

5.3 Méthodes avancées


# Merging dictionaries (Python 3.9+)
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}
merged_dict = d1 | d2

# Conditional deletion
keys_to_delete = [k for k, v in config.items() if isinstance(v, bool)]
for k in keys_to_delete:
config.pop(k)

# Grouping data
from collections import defaultdict
data = [("fruit", "apple"), ("vegetable", "carrot"), ("fruit", "banana")]
grouped = defaultdict(list)
for category, name in data:
grouped[category].append(name)

6. Copie et Références

6.1 Types de copie


# Reference copy
original = {"a": 1, "b": [2, 3]}
ref = original
ref["a"] = 10

# Shallow copy
import copy
shallow_copy = original.copy()
shallow_copy["b"].append(4)

# Deep copy
deep_copy = copy.deepcopy(original)
deep_copy["b"].append(5)

6.2 Gestion de la mémoire

  • Les dictionnaires utilisent une table de hachage pour un accès rapide
  • Surcoût mémoire d'environ 40% par rapport aux données brutes
  • Méthode __slots__ pour optimiser les objets personnalisés

7. Itération

7.1 Méthodes d'itération


# Classic iteration
for key in config:
print(f"{key}: {config[key]}")

# Using items()
for key, value in config.items():
print(f"{key.upper()} -> {value}")

# Iteration with enumerate
for i, (key, value) in enumerate(config.items(), 1):
print(f"{i}. {key}: {value}")

7.2 Itération avancée


# Sorting elements before iteration
for key, value in sorted(config.items(), key=lambda x: x[0].lower()):
print(f"{key}: {value}")

# Merging iterators
from itertools import chain
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
for item in chain(dict1.items(), dict2.items()):
print(item)

8. Performance

8.1 Complexité des opérations

Opération Complexité Exemple
Accès par clé O(1) d['key']
Insertion O(1) d['new'] = value
Suppression O(1) del d['key']
Recherche de clé O(1) 'key' in d
Itération O(n) for k, v in d.items()

8.2 Optimisations

  • Privilégier les clés immuables et hashables
  • Utiliser des dictionnaires imbriqués pour les données structurées
  • Employer collections.OrderedDict pour maintenir l'ordre avant Python 3.7
  • Utiliser dictviews pour les opérations sur les ensembles de clés/valeurs

9. Practical Use Cases

9.1 Configuration Storage


# Application configuration
config = {
"database": {
"host": "localhost",
"port": 5432,
"credentials": {
"user": "admin",
"password": "secret"
}
},
"logging": {
"level": "DEBUG",
"file": "/var/log/app.log"
}
}

9.2 Data Aggregation


# Occurrence counting
from collections import defaultdict

votes = ["Python", "Java", "Python", "C++", "Java", "Python"]
results = defaultdict(int)
for language in votes:
results[language] += 1

10. Exercises

10.1 Implementation Exercise

Create an inverted dictionary where values become keys and keys become values. Handle potential conflicts by storing values in lists.


# Implementation exercise
original = {"a": 1, "b": 2, "c": 1, "d": 3}
inverted = {}

for key, value in original.items():
if value not in inverted:
inverted[value] = [key]
else:
inverted[value].append(key)

print(inverted)  # {1: ['a', 'c'], 2: ['b'], 3: ['d']}

10.2 Performance Exercise

Compare access times between a standard dictionary and a collections.OrderedDict with 1 million entries.


# Performance comparison
from collections import OrderedDict
import time

# Standard dictionary
start = time.time()
std_dict = {str(i): i for i in range(1000000)}
for i in range(1000000):
_ = std_dict[str(i)]
std_time = time.time() - start

# OrderedDict
start = time.time()
ord_dict = OrderedDict((str(i), i) for i in range(1000000))
for i in range(1000000):
_ = ord_dict[str(i)]
ord_time = time.time() - start

print(f"Standard Dict: {std_time:.2f}s")
print(f"OrderedDict: {ord_time:.2f}s")

10.3 Exercice avancé

Implémentez un cache LRU (Least Recently Used) en utilisant uniquement des structures de base du Python. Ce type de cache est très utile pour optimiser les performances en gardant en mémoire les éléments les plus récemment utilisés tout en supprimant les moins utilisés lorsque la capacité est atteinte.

Le cache LRU est largement utilisé dans les systèmes informatiques pour améliorer les performances, notamment dans :

  • Les systèmes de bases de données pour mettre en cache les requêtes fréquentes
  • Les navigateurs web pour stocker les pages visitées récemment
  • Les systèmes d'exploitation pour la gestion de la mémoire

# LRU Cache implementation
class LRUCache:
def __init__(self, capacity):
self.cache = {}
self.capacity = capacity
self.order = []

def get(self, key):
if key not in self.cache:
return -1
# Move to end to mark as recently used
self.order.remove(key)
self.order.append(key)
return self.cache[key]

def put(self, key, value):
if key in self.cache:
self.order.remove(key)
elif len(self.cache) >= self.capacity:
# Remove least recently used item
lru = self.order.pop(0)
del self.cache[lru]

self.cache[key] = value
self.order.append(key)

# Usage example
cache = LRUCache(2)
cache.put(1, 1)  # cache is {1=1}
cache.put(2, 2)  # cache is {1=1, 2=2}
cache.get(1)     # returns 1
cache.put(3, 3)  # removes key 2, cache is {1=1, 3=3}
cache.get(2)     # returns -1 (not found)

11. Résumé et Comparaisons

Les dictionnaires sont des structures de données fondamentales en Python qui offrent une grande flexibilité et efficacité pour stocker et manipuler des données associatives. Voici un récapitulatif des caractéristiques principales :

Structure Mutable Ordonné Accès Cas d'usage
Dictionnaire Oui Oui (≥3.7) Clé Données associatives
Liste Oui Oui Index Séquences ordonnées
Ensemble Oui Non Test appartenance Éléments uniques

Les dictionnaires se distinguent par leur capacité à associer des valeurs à des clés uniques, ce qui permet un accès rapide et efficace aux données. Contrairement aux listes qui utilisent des indices numériques, les dictionnaires peuvent utiliser presque n'importe quel type de données immuable comme clé, offrant ainsi une grande flexibilité.

Depuis Python 3.7, les dictionnaires conservent l'ordre d'insertion des éléments, ce qui les rend encore plus polyvalents pour représenter des données structurées.

12. Conclusion

Les dictionnaires sont l'une des structures de données les plus puissantes et polyvalentes en Python. Leur capacité à stocker des paires clé-valeur avec un accès rapide en fait un outil indispensable pour tout développeur Python.

Points Clés

  • Structure optimisée pour les accès par clé avec une complexité O(1)
  • Grande variété de méthodes de manipulation pour différentes opérations
  • Implémentation basée sur des tables de hachage pour des performances optimales
  • Préservation de l'ordre d'insertion depuis Python 3.7
  • Possibilité de stocker des valeurs de types différents dans un même dictionnaire

Bonnes Pratiques

  • Utiliser des clés descriptives et immuables pour une meilleure lisibilité et stabilité
  • Privilégier les dictionnaires natifs aux classes personnalisées simples pour des raisons de performance
  • Utiliser les compréhensions de dictionnaires pour les transformations de données concises
  • Préférer la méthode get() à l'accès direct par clé pour éviter les exceptions
  • Exploiter les structures spécialisées comme defaultdict et Counter pour des cas d'usage spécifiques
  • Considérer l'utilisation de deepcopy() pour éviter les modifications non intentionnelles des structures imbriquées

Maîtriser les dictionnaires est essentiel pour écrire du code Python efficace et élégant. Que ce soit pour stocker des configurations, traiter des données JSON, implémenter des caches ou créer des structures de données complexes, les dictionnaires offrent la flexibilité et les performances nécessaires pour répondre à une multitude de besoins de programmation.

That's all folks