Comprendre l'exception TypeError: unhashable type: 'numpy.ndarray'
Lors du développement en Python, il est fréquent de rencontrer l'erreur TypeError: unhashable type: 'numpy.ndarray'. Cette exception se déclenche principalement lorsqu'un développeur tente d'utiliser un tableau NumPy comme clé dans un dictionnaire ou comme élément dans un ensemble (set). Pour résoudre ce problème, il est nécessaire de convertir la structure de données mutable en un type immuable, tel qu'une chaîne d'octets ou un tuple.
Origine du problème
En Python, le mécanisme de hachage (hashing) permet de mapper des données vers des valeurs numériques uniques. Pour qu'un objet soit hachable, il doit être strictement immuable. Les structures de données comme les listes (list) ou les tableaux NumPy (numpy.ndarray) sont mutables par nature. Par conséquent, le moteur Python interdit leur utilisation comme clés de dictionnaire et lève une TypeError.
Les situations courantes incluent :
- L'insertion d'un tableau NumPy en tant que clé de dictionnaire.
- L'ajout d'un tableau NumPy dans un ensemble (
set). - L'indexation de structures de données complexes avec des tableaux bruts.
Reproduction de l'erreur
Le code suivant illustre une tentative invalide d'utilisation d'un tableau comme clé :
import numpy as np
# Initialisation d'un tableau numérique
numeric_grid = np.array([10, 20, 30])
# Tentative d'utilisation comme clé de dictionnaire
data_mapping = {numeric_grid: "valeur_cible"}
L'exécution de ce script génère immédiatement l'erreur suivante :
TypeError: unhashable type: 'numpy.ndarray'
Stratégies de résolution
Pour contourner cette limitation, il faut transformer le tableau en une structure immuable. Voici deux approches efficaces.
Approche 1 : Conversion en séquence d'octets
La méthode tobytes() permet de sérialiser le contenu du tableau en une chaîne d'octets, qui est parfaitement hachable. (Note : l'ancienne méthode tostring() est dépréciée).
import numpy as np
numeric_grid = np.array([10, 20, 30])
# Sérialisation en octets
byte_sequence = numeric_grid.tobytes()
# Utilisation de la séquence d'octets comme clé
data_mapping = {byte_sequence: "valeur_cible"}
print(data_mapping)
Approche 2 : Conversion en tuple
Une autre méthode consiste à extraire les éléments du tableau pour les placer dans un tuple. Les tuples étant immuables, ils peuvent servir de clés.
import numpy as np
numeric_grid = np.array([10, 20, 30])
# Conversion explicite en tuple
immutable_sequence = tuple(numeric_grid)
# Utilisation du tuple comme clé
data_mapping = {immutable_sequence: "valeur_cible"}
print(data_mapping)
Application pratique : Reconnaissance faciale avec Dlib
Dans les projets de vision par ordinateur, comme la reconnaissance faciale, les descripteurs de caractéristiques (embeddings) sont souvent des vecteurs numériques. Stocker ces vecteurs dans un dictionnaire pour identifier des individus nécessite une conversion préalable.
Implémentation correcte
Voici comment extraire des descripteurs faciaux et les stocker de manière sécurisée en utilisant des tuples :
import cv2
import dlib
import numpy as np
# Chargement des modèles pré-entraînés
face_detector = dlib.get_frontal_face_detector()
landmark_predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
recognition_model = dlib.face_recognition_model_v1('dlib_face_recognition_resnet_model_v1.dat')
def extract_embedding(image_path):
img = cv2.imread(image_path)
detections = face_detector(img, 1)
if not detections:
return None
landmarks = landmark_predictor(img, detections[0])
return recognition_model.compute_face_descriptor(img, landmarks)
# Extraction des caractéristiques pour deux images
embedding_a = extract_embedding('assets/subject_a.jpg')
embedding_b = extract_embedding('assets/subject_b.jpg')
# Conversion des vecteurs Dlib/NumPy en tuples hachables
hashable_a = tuple(embedding_a)
hashable_b = tuple(embedding_b)
# Enregistrement dans un dictionnaire sans erreur
identity_registry = {
hashable_a: "Subject_Alpha",
hashable_b: "Subject_Beta"
}
# Calcul de la distance euclidienne pour vérification
distance = np.linalg.norm(np.array(embedding_a) - np.array(embedding_b))
match_status = "Identique" if distance < 0.6 else "Distinct"
print(f"Distance: {distance:.4f} -> Statut: {match_status}")
En convertissant les vecteurs de caractéristiques en tuples, l'exception TypeError est évitée, permettant ainsi un stockage et une comparaison efficaces des identités dans des structures de données hachables.