Les collections





Collection: tout ensemble ordonné ou non ordonné, contenant des éléments, soit de même type soit hétérogènes
 
Le terme collection a été préféré à conteneur de données (traduction littérale de « data container ») pour éviter de les confondre avec les bases de données (relationnelles). Qu’elles soient ordonnées ou pas, toutes ces collections ont en commun d’être dénombrables (iterable en anglais).
Les ensembles ordonnés sont aussi appelés séquences.
A l’intérieur de cette catégorie (les séquences), on distingue celles qui sont modifiables (mutable) et les autres (immutable).
 
Résumons: les collections peuvent être

  • modifiées ou pas (mutable/immutable): nous verrons ce que cela signifie concrètement
  • ordonnées, c’est à dire contenant des éléments occupant un rang précis (indexation), ou pas
  • hétérogènes, c’est à dire pouvant contenir des éléments de types différents, ou pas

 
Le tableau suivant permet de synthétiser les propriétés de chaque objet

Objet Modifiable Ordonné Hétérogène
texte ( str) non oui non
tuplet ( tuple) non oui oui
liste ( list) oui oui oui
dictionnaire ( dict) oui non oui
ensemble ( set) non non oui

Cette présentation aborde les thèmes suivants

 

Généralités sur les séquences

 
Les séquences sont des ensembles ordonnés, car chaque élément y occupe une place (ou rang, ou numéro, ou index, au choix), déterminée au moment de son enregistrement, sachant que le 1er élément enregistré a le numéro 0. Par conséquent, pour extraire l’élément qui occupe le rang n à l’intérieur d’une séquence appelée seq, on le désignera par seq[n].
Il y a 3 types de séquences en Python: les chaînes de caractères (texte), les listes et les tuplets. Par exemple, pour extraire le caractère numéro 10 du texte ´ Bonjour tout le monde !´, on écrit

 

 

Le tableau ci-dessous énumère quelques méthodes communes aux trois types de séquences
 

len(seq) renvoie le nombre d’éléments de seq
count(x) renvoie le nombre d’éléments égaux à x
index(x) renvoie l’index du 1er élément égal à x
s. join(seq) renvoie la concaténation des éléments d’une séquence, séparés par un caractère s

 

Pour différencier les séquences, puisqu’il n’y a pas de déclaration explicite de type au moment de la création, on met les éléments entre crochets (cas des listes), entre parenthèses (cas des tuplets) ou entre apostrophes (les guillemets ça marche aussi) pour les chaînes de caractères.

 

 
Pour extraire un groupe d’éléments à partir d’une séquence, on utilise les expressions suivantes

seq[n:p] extrait les éléments entre les rang n et p-1
seq[n:] tous les éléments à partir du rang n
seq[:n] tous les éléments jusqu’au rang n-1
seq[:] tous les éléments de la séquence
seq[len(seq)-1] (ou seq[-1]) le dernier élément de la séquence
seq[-n:-p] idem que seq[len(seq)-n:len(seq)-p]

On peut, quel que soit le type de séquence, ajouter ou enlever plusieurs éléments à la fois, concaténer (réunir) plusieurs séquences, les multiplier, etc.
Pour ajouter un ou plusieurs éléments, on emploie simplement le signe + (plutôt l’opérateur += puisqu’il s’agit de remplacer l’ancienne séquence après addition). Pour un tuplet, ne pas oublier la virgule après la valeur quand on ajoute un singleton (dans l’exemple qui suit, la virgule qui suit la valeur 5 est en rouge)

 

 

 
NB: si l’on oublie la virgule, c’est à dire qu’on déclare t=(5), Python l’interprète comme une valeur entière et l’opération t+=(6,) est impossible

 

 
Pour clore ce paragraphe traitant des séquences en général, précisons qu’il est possible de créer des séquences vides, en attente d’être renseignées.

 

 
La possibilité de réserver un emplacement en mémoire à une valeur non déterminée semble en contradiction avec ce qui précède (rappel: c’est l’objet qui donne vie à la variable). Ca l’est moins en observant que les séquences, comme les variables simples, sont définies par des classes d’objets, c’est à dire des attributs et des méthodes: dans le chapitre § Classes d’objets personnalisés, nous verrons qu’il est possible de créer (instancier) un objet en omettant de renseigner un ou plusieurs membres (dans un objet vide, ce sont les attributs qui sont omis).

Création de séquences « en compréhension »

 
La notion de liste en compréhension a été brièvement abordée dans le chapitre § Tests et boucles: nous allons, dans ce paragraphe, et dans celui consacré à la création de tableaux, aller plus loin.
Pour rappel, l’expression « en compréhension » désigne la manière de créer et d’initialiser une séquence à l’aide d’une expression fonctionnelle, la plupart du temps une boucle: si l’on veut initialiser une liste, cette expression devra être entre crochets. Nous verrons que cela s’applique aux ensembles non ordonnés, comme les dictionnaires et les ensembles.
L’emploi de cette méthode nécessite qu’il y ait un lien fonctionnel entre les éléments de la séquence. Sa forme la plus courante est

[f(x) for x in seq [condition (facultatif)]]

où seq est la séquence parcourue par la variable x et f est la fonction permettant le calcul des éléments de la liste à initialiser
L’exemple suivant traite le cas d’une liste initialisée avec les carrés des 10 premiers entiers (en partant de 1)
 

 
Si on veut restreindre les carrés aux nombres divisibles par 3
 

 
Le lien fonctionnel peut atteindre une certaine complexité et faire intervenir la fonction filter (voir Fonctions): nous ne détaillerons pas ici toutes les possibilités offertes par cette méthode qui fait les délices des développeurs confirmés. Dans le paragraphe consacré à la manipulation des listes, nous aborderons l’utilisation de cette méthode pour créer des tableaux (listes à 2 ou 3 dimensions) à partir de listes déjà renseignées.
Outre les listes, cette méthode s’applique aux conteneurs de types dict et set. Avec les chaînes de caractères, elle s’utilise, associée à la fonction join .
Exemple: création d’une chaîne de caractères contenant les 10 premières lettres de l’alphabet:
 

 

La définition d’un tuplet en compréhension produit un objet de type générateur, et non de type tuple (voir § Générateurs).
 

Manipulation des tuplets

 
Les tuplets ne sont pas modifiables en ce sens qu’on ne peut pas changer l’adresse de ses éléments. En d’autres termes, chaque élément pris séparément ne peut pas être remplacé par un autre. Le message d’erreur qui apparaît quand on veut assigner une nouvelle valeur à un élément est

‘tuple’ object does not support item assignment

Cela ne veut pas dire qu’un élément, lui-même modifiable, ne puisse pas subir de changements « internes »: en effet, par définition, les variables « mutables » sont celles qui peuvent changer de valeur en conservant la même adresse.
Pour illustrer ce point, prenons l’exemple d’un tuplet t contenant des éléments hétérogènes, à savoir 3 variables entières, une chaîne de caractères et une liste.
Quel que soit l’élément, une tentative de le changer « en bloc » se solde par un message d’erreur:

 

 
En revanche, on peut modifier chaque élément de la liste [0,1,2], sachant que l’adresse de cette dernière ne va pas changer (les listes sont « mutables »). Pour désigner un élément de cette liste, il faut utiliser un deuxième indice. Par exemple, le 3ème élément de la liste qui occupe la 4ème place dans le tuplet, est t[3][2] (le 1er indice désigne la place de la liste dans le tuplet, le 2ème désigne la place de l’élément dans la liste).

 

 
On peut utiliser aussi un artifice pour modifier d’un bloc une liste à l’intérieur d’un tuplet, ce qui revient à remplacer cette liste sans changer son adresse. Il s’agit d’extraire les valeurs contenues dans la liste et de les remplacer en une seule fois par de nouvelles valeurs. Cela est possible car, en extrayant les éléments d’une liste, Python crée une copie de cette liste, c’est à dire un objet distinct, avec une adresse distincte, mais contenant les mêmes éléments que la liste copiée (forcément…).
Confirmation en images

 

 
On remplace les valeurs dans la copie de t[4], alias t[4][:]. Cette modification se transmet à t[4], qui conserve toujours son adresse originale.

 

 
Pour clore ce paragraphe, rappelons que l’ajout d’un élément à un tuplet doit se faire à l’aide d’une virgule à la suite de l’élément à rajouter. Pour créer un tuplet contenant d’autres tuplets, un exemple vaut mieux qu’un long discours

 

 

Manipulation des listes

 
Dans le paragraphe précédent, nous avons vu un aspect des séquences (hétérogènes) de type list, qui est leur « mutabilité » (aptitude à être modifiées sans devoir changer d’adresse). La modification d’un élément se fait très simplement. On vérifie, dans l’exemple ci-dessous que l’adresse de l’élément modifié a bougé, pas celle de la liste
 

 
On peut renseigner une liste à l’aide de variables qui font référence à des objets existants
 

 
Inversement, on peut démembrer une liste

 

 
Pour augmenter une liste, on peut faire appel à une assignation simple (=), composée (+=), ou bien faire appel aux méthodes append et extend. L’exemple qui suit montre que append permet de rajouter les éléments seulement 1 à 1. On notera que l’addition d’un tuplet à une liste revient à additionner deux listes:
 


 

Le tableau ci-dessous énumère quelques méthodes propres aux listes

insert(i,x) insére x à l’index i
remove (x) supprime le 1er élément égal à x
pop(i) supprime l’élément dont l’index est i et le renvoie
reverse(x) inverse l’ordre d’une liste
__delitem__(i) supprime l’élément dont l’index est i
__delslice__(i,j) supprime les éléments de l’indice i (inclus) à l’indice j (exclu)
sort() trie dans l’ordre ascendant

 

 
Les fonctions sort, reverse, remove ou __delslice__ ne renvoient aucun résultat, c’est à dire la valeur None, elles se contentent de transformer une liste (à la manière des fonctions déclarées avec void en C/Java ou des subroutines en VBA). Pour visualiser une liste triée, on peut utiliser la fonction sorted, mais cette fonction ne modifie pas la liste originale

 

 
Etant modifiable, il est courant de copier une liste avant d’effectuer des changements dessus. Toutefois, probablement par souci d’économie de mémoire, Python attribue la même adresse à la liste originale et à sa copie: le nom de la copie ne représente en pratique qu’un pseudonyme (ou alias), comme dans le cas des variables simples (rappelons l’analogie; plusieurs occupants peuvent habiter à la même adresse). Les changements qui affecteront une liste affecteront automatiquement l’autre.
Nous avons vu (cf tuplets), que l’extraction du contenu d’une liste se traduisait par la création d’une liste distincte, c’est à dire ne partageant pas la même adresse: cette extraction crée de facto une (vraie) copie.
Une autre solution est d’utiliser la fonction copy (dans le module copy)

 

 
Lorsque la liste à copier contient elle-même des listes ou dictionnaires, qui sont de type modifiable, les changements apportés à ces éléments n’affectent pas leur adresse, par nature. On s’aperçoit alors que la fonction copy ne va pas assez loin, car tout changement d’un élément modifiable se répercute sur l’élément de la liste copiée. La fonction qui permet d’obtenir une réelle indépendance en créant des adresses distinctes pour les éléments modifiables d’une liste est la fonction deepcopy (toujours dans le module copy)

 

 

Création de tableaux (listes de listes)

 
Au paragraphe § Généralités sur les séquences, nous avons vu la création de listes « par compréhension ». Cette méthode peut être appliquée pour créer des listes contenant elles mêmes des listes.
Prenons le cas d’une liste qui contient n listes, chacune contenant m valeurs: cet objet est assimilable à un tableau de n lignes et m colonnes, même si,dans son affichage, on ne distingue pas de colonnes (pour cela, il faut utiliser l’extension du langage proposé par numpy).
Par exemple, le tableau 3×2 suivant

correspond à la liste

l=[[2, 3], [4, 6], [6, 9]]

Dans la matrice ci-dessus, on distingue la relation suivante: chaque ligne est obtenue en multipliant la liste [2,3] par 1, puis 2 et 3, c’est à dire par le n° de la ligne en question. Sachant que

[2,3] ==[i for i in range(2,4)]

on définit cette matrice en compréhension par la relation suivante

 

 
Ici, il y a une boucle principale, indicée par j qui parcourt le nombre de lignes, et une boucle secondaire indicée par i, qui parcourt les éléments de chaque ligne ([1*2, 1*3],[2*2, 2*3], [3*2, 3*3]).
Les crochets ‘intérieurs’ sont importants pour créer le tableau à deux dimensions: sans eux, la liste créée ne contient que des valeurs simples, et l’ordre dans lequel sont effectuées les multiplications est inversé

 

 
Dans cette liste, chaque élément i de range(2,4) successivement multiplie chaque élément j de range(1,4) ([2*1, 2*2, 2*3, 3*1, 3*2, 3*3])
A titre de confirmation,
 

 
Avec trois indices, on applique les mêmes règles en ce qui concerne l’ordre d’appel des indices
 

 
Dans le détail,

j k [i*j*k for i in range(2,4)]
1 1 [2,3]
1 2 [4,6]
2 1 [4,6]
2 2 [8,12]
3 1 [6,9]
3 2 [12,18]

Pour les tableaux à 3 dimensions, il faut rajouter une paire de crochets
 

 

k j in range(1,4) [[i*j*k for i in range(2,4)] for j in range(1,4)]
1 j in range(1,4) [[2, 3], [4, 6], [6, 9]]
2 j in range(1,4) [[4, 6], [8, 12], [12, 18]]

 

Les dictionnaires

 
Pour déclarer un dictionnaire, on peut utiliser la fonction dict, ou bien placer ses éléments entre accolades (ou bien les deux)
Chaque élément associe, une clé qui sert d’index, non modifiable, et une valeur.
 
L’analogie avec le dictionnaire de la vie courante (Larousse, Robert…) est évidente: on peut ajouter une signification à un mot, mais pas l’orthographe de ce mot: les mots, qui ne peuvent être modifiés, sont les clés et les explications accompagnant ces mots font office de valeurs.
L’ensemble des valeurs (values())d’un dictionnaire est un objet de type list, ainsi que l’ensemble des clés (keys())

 

 
Dans un dictionnaire, l’ordre ne compte pas
 

 
Pour obtenir un dictionnaire trié, on dispose d’une extension du type dict, OrderedDict, défini dans le module collections. En pratique, OrderedDict est une fonction qui permet de transformer un dictionnaire désordonné en objet OrderedDict, trié par clé ou par valeur. Pour trier par clé un dictionnaire appelé d, la syntaxe est la suivante

OrderedDict(sorted(d.items(), key=lambda t: t[0]))

Pour trier par valeur, remplacer t[0] par t[1] (l’identifiant t est purement formel)
Illustration:
 

 
Pour additionner deux dictionnaires, on utilise les clés
 

 
Pour que l’addition des deux dictionnaires s’effectue dans un troisième dictionnaire, ne pas omettre d’effectuer une copie de a via la fonction copy (sinon le premier dictionnaire est modifié en même temps).
L’instruction suivante permet de créer ce troisième dictionnaire, de façon encore plus synthétique
 

 
Cette méthode a le désavantage d’offrir un résultat désordonné.

Les ensembles

 
Les ensembles sont non ordonnés et leurs éléments ne sont pas indexés: ils sont, pour ainsi dire, en vrac. Les éléments d’un ensemble sont représentés entre crochets, comme arguments de la fonction set (pour ne pas les confondre avec les listes). Les ensembles ignorent les doublons.
 

 
Pour agréger ou effectuer des rapprochements entre ensembles, on utilise les opérateurs |, &, –