7. Les types composés#
Mis à jour : May 15, 2025, lecture : 15 minutes minimum, PhL.
Les types composés ou conteneurs de python sont introduits dans ce chapitre :
les types non mutables de python :
tuple
etstr
les types mutables de python :
list
,set
etdict
Rmq de vocabulaire :
J’utilise les termes mutable et non mutable.
La version française de la documentation de python préfère les termes muable et immuable.
7.1. Les types composés ou conteneurs en python#
Les types scalaires suivants permettent de manipuler des valeurs scalaires :
bool
int
float
complex
les caractères (souvent dénotés de type char mais absent en python)
Un type composé permet de regrouper plusieurs valeurs, de même type ou non, scalaire ou non, dans une seule variable (ou un seul objet).
Vocabulaire. Ces types composés sont appelés des conteneurs en python. Ce sont aussi des premiers exemples de structures de données en algorithmique.
Exercice. Vous en connaissez déjà au moins un. Lequel ?
7.1.1. Les questions abordées dans ce chapitre#
Comment créer un conteneur ?
Comment accéder à une valeur dans un conteneur ?
Comment modifier un conteneur ?
Comment modifier une de ses valeurs ?
Comment ajouter ou supprimer une valeur ?
Comment parcourir (toutes) les valeurs d’un conteneur ?
Les réponses à ces questions dépendent :
du type de conteneur,
de son caratère mutable ou non,
de son caractère itérable ou non,
de son caractère séquençable ou non.
(\(\star\)) Les deux dernières caractéristiques (itérable, séquençable) seront abordées en seconde lecture.
(\(\star\)) On analysera aussi la complexité des ces différents traitements selon les principaux types composés.
7.1.2. La vision classique#
Dans la plupart des langages de programmation, les types composés classiques sont les suivants.
Les tableaux, déjà revus dans ce chapitre, sont définis comme des ensembles de valeurs de même type et de nombre connu et fixé “une fois pour toute” lors de leur définition (ce nombre est la taille ou la dimension du tableau) et dont les valeurs sont ainsi stockées de façon contigüe en mémoire.
- les valeurs sont accédées par un ou des indice indiqué entre un ou plusieurs []
Les enregistrements ou structures sont définis comme un ensemble de valeurs de types quelconques. Le nombre de valeurs est défini “une fois pour toutes” mais sans hypothèse sur le mode de stockage.
- les enregistrements sont composés de champs nommés aussi appelés attributs
- la valeur stockée dans le champ d’un enregistrement est accessible en notation pointée : id_de_variable.nom_du_champ
.
Aucun type composé natif de python ne correspond exactement aux 2 types composés classiques tableau et enregistrement.
Le module numpy
définit “de vrais” tableaux, les ndarray
, que nous abordons dans cette annexe.
Exemple : Une structure etudiant
pourrait être définie à partir des 4 champs suivants :
etudiant.numero_etud
,etudiant.nom
,etudiant.prenom
etetudiant.diplome
.
7.1.3. Les conteneurs python#
Vocabulaire python : un conteneur est une variable (un objet) de type composé
Les conteneurs python sont répartis en 2 familles.
Les conteneurs non mutables dont la forme et les valeurs sont fixées une fois pour toute lors de leur définition :
les chaines de caractères :
str
écrits entre des" "
ou des' '
les t-uplets ou
tuple
: des valeurs écrites entre( )
et séparées par des,
les ensembles ou
set
: des valeurs écrites avec des{ }
et séparées par des,
Les conteneurs mutables qui peuvent changer de forme et de valeurs.
des listes ou
list
: des valeurs écrits entre[ ]
et séparées par des,
des dictionnaires (tableaux associatifs) ou
dict
: des couples de valeurs écrits entre{ }
et séparées par des,
les fichiers de texte
Les fichiers (le type file
) sont aussi des conteneurs.
Ils ne sont pas abordés ici et font l’objet de ce chapitre.
7.2. Ce qui est commun aux conteneurs python#
7.2.1. Opérations communes aux conteneurs python#
itération sur les valeurs d’un conteneur :
for val in conteneur:
pythonerie utile en pratique pour parcourir les valeurs d’un conteneur sans se soucier de son “indice”
cette itération s’effectue indépendamment de l’existence ou non d’un ordre sur les valeurs du conteneur, ie. séquence ou non séquence
on reviendra sur cette notion, en particulier pour les dictionnaires
tous les conteneurs sont ainsi des itérables.
accès à l’élément par indexation pour les séquences et dictionnaires :
v[1]
,m[3][2]
,st[-1]
,tuple[-len(tuple)]
,d[clé]
L‘“indice” est indiqué entre
[ ]
Cet indice est un/des entier/s sauf pour les dictionnaire : notion de “clé”
Un conteneur ordonné par des indices entiers est appelé une séquence.
tranche (slice) d’un conteneur séquence :
conteneur[i:j]
est composé des valeurs de l’indicei
àj-1
Des fonctions de base qui s’appliquent à une variable conteneur :
len()
qui renvoie le nombre de valeurs du conteneurmin()
,max()
,sorted()
si une relation d’ordre s’applique aux éléments d’un conteneur (itérable)Rmq:
sorted()
créé un nouveau conteneurlist
.
Exercice. Que pensez-vous de la variable v
définie par v = range(10)
par exemple ?
v = range(10)
print(v)
for val in v:
print(val, end=",")
print()
print(type(v), type(val))
range(0, 10)
0,1,2,3,4,5,6,7,8,9,
<class 'range'> <class 'int'>
Réponse : v
peut être vue comme un ensemble ordonné itérable de valeurs entières. Ici le terme ensemble est à comprendre au sens mathématique ; il ne s’agit pas des ensembles set
de python présentés plus loin.
L’exemple suivant illustre la différence entre un tableau et un conteneur de type list
.
from math import exp
en_vrac = ['a','z','e','r','t', 1, 2, 3, 3.14159, exp(1.0)]
print(type(en_vrac), len(en_vrac))
<class 'list'> 10
Et maintenant quelques manipulations avec les opérations de base sur ces conteneurs.
print(en_vrac)
# conversion de type : list -> tuple
des_float = tuple(en_vrac[-2:])
# len, min, max
print(des_float, len(des_float), min(des_float), max(des_float), end="\n")
['a', 'z', 'e', 'r', 't', 1, 2, 3, 3.14159, 2.718281828459045]
(3.14159, 2.718281828459045) 2 2.718281828459045 3.14159
# accès par indexation et tranche
print(en_vrac)
print(en_vrac[0], en_vrac[-3])
print(des_float)
print(des_float[1], des_float[1:3])
#print(type(en_vrac[0]), type(en_vrac[-3]), type(des_float[1]), type(des_float[1:3]))
['a', 'z', 'e', 'r', 't', 1, 2, 3, 3.14159, 2.718281828459045]
a 3
(3.14159, 2.718281828459045)
2.718281828459045 (2.718281828459045,)
# les tranches de conteneurs sont souvent très utiles.
print(en_vrac[1:-1])
print(des_float[1:3])
['z', 'e', 'r', 't', 1, 2, 3, 3.14159]
(2.718281828459045,)
# conversion en `str` et concaténations
print(en_vrac)
st = ''
for c in en_vrac[0:-2]:
#st = st + c # non : normal !
st = st + str(c) # concatenation de chaines de caractères
print(st, type(st))
['a', 'z', 'e', 'r', 't', 1, 2, 3, 3.14159, 2.718281828459045]
azert123 <class 'str'>
Le caractère non mutable d’une str
explique le comportement suivant.
# sorted sur `str` retourne une `list`
print(st)
print(sorted(st), type(sorted(st)))
st2 = str(sorted(st))
print(st2, type (st2))
print( len(sorted(st)), len(st2) )
azert123
['1', '2', '3', 'a', 'e', 'r', 't', 'z'] <class 'list'>
['1', '2', '3', 'a', 'e', 'r', 't', 'z'] <class 'str'>
8 40
sorted(en_vrac)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[8], line 1
----> 1 sorted(en_vrac)
TypeError: '<' not supported between instances of 'int' and 'str'
Exercice. Expliquer le résultat du dernier affichage.
Il y a 40 caractères dans st2
.
Le premier est un []
, puis un '
, …
On retrouve 40 avec 8 répétitions de 5 caractères (les
et les [
et ]
donnent 8 motifs de même longueur 5)
S’en convaincre en tapant par exemple :
print(st2[0])
7.2.2. Conteneurs itérables et séquences#
Tous les conteneurs sont (des) itérables.
la construction suivante a du sens et permet le parcours des valeurs du conteneur
for val in cont:
...
Mais tous les conteneurs ne sont pas des séquences.
Une séquence est un conteneur indexé par des entiers
types python :
tuple
,list
etstr
les valeurs sont ainsi ordonnées dans le conteneur
attention : on n’a pas écrit que les valeurs étaient ordonnées selon leur valeur
Les dictionnaires dict
et les ensembles set
sont itérables mais pas des séquences.
Fonctions supplémentaires
cont
est un conteneur.
concaténation :
cont1 + cont2
duplication (concaténations répétées) :
cont * cst
reversed(cont)
renvoie le conteneur itérable en ordre inverse
La concaténation et la duplication peuvent être des opérations coûteuses, en espace mémoire en particulier, selon le caractère mutable ou non de la variable concernée.
Et aussi les 2 fonctions-méthodes suivantes dont on n’oubliera pas qu’elle peuvent aussi “coûter très cher” :
correspondance valeur -> indice :
cont.index(val)
retourne le plus petit (ou premier) indice d’une valeur argument présente dans le conteneurcont
, génère une exception si la valeur est absentedécompte :
cont.count(val)
retourne le nombre d’occurrences deval
dans le conteneurcont
print(en_vrac[0:-2])
['a', 'z', 'e', 'r', 't', 1, 2, 3]
st = ''
for c in reversed(en_vrac[0:-2]):
st = st + str(c) # concatenation de chaines de caractères
print(st, sorted(st))
321treza ['1', '2', '3', 'a', 'e', 'r', 't', 'z']
st1 = st + st
st2 = st * 2
print(st, st1, st1 == st2)
print(st.count('a'))
print(st.index('a'))
321treza 321treza321treza True
1
7
7.3. Les tuple
#
tuple
( ou t-uplets ) : conteneur non mutable de valeurs écrites entre ( )
, séparées par des ,
, et indexées de 0 à len(tuple)-1
.
(3, “bonjour”, 7, 1.32)
Bien identifier les 4 aspects qui définissent un tuple
En pratique, les parenthèses sont facultatives
donc un tuple singleton peut être noté :
a,
Ne pas oublier la
,
!!
t = (3,)
print(type(t))
# ATTENTION !
t = (3)
print(type(t))
t = ('3')
print(type(t))
t = 3,
print(type(t))
<class 'tuple'>
<class 'int'>
<class 'str'>
<class 'tuple'>
On préférera donc écrire les tuples systématiquement entre parenthèses.
# premières manipulations de tuple
c = (1,2,3)
c2 = c + c
print(c2)
d1 = c * 3
c = c + (4,)
print(c, d1)
(1, 2, 3, 1, 2, 3)
(1, 2, 3, 4) (1, 2, 3, 1, 2, 3, 1, 2, 3)
Attention.
Les
tuple
sont non mutables donc modifierc
(ici par concaténation à l’avant-dernière ligne) créé en fait une nouvelle variablec
.L’exécution pythontutor illustre bien ce point important.
Exercice. Que penser de la fonction à la spécification incomplète suivante ?
def permuter(a : int, b : int) :
'''il manque la spécification de la sortie'''
return b, a
x, y = 3, 7
print(x, y, type(x), type(y))
x, y = permuter(x, y)
print(x, y, type(x), type(y))
(u, v) = permuter(x, y)
print(u, v, type(u), type( (u, v) ), type( (u, v)[0] ) )
3 7 <class 'int'> <class 'int'>
7 3 <class 'int'> <class 'int'>
3 7 <class 'int'> <class 'tuple'> <class 'int'>
#d1[2] = 7 lève TypeError: 'tuple' object does not support item assignment
# manipulations de tuple avec les fonctions de base
print(c, d1)
print(d1[1], d1.index(3))
print(d1.count(3))
d1 = c * 2
print(c, d1)
print(d1[1], d1.index(3))
print(d1.count(3))
(1, 2, 3, 4) (1, 2, 3, 1, 2, 3, 1, 2, 3)
2 2
3
(1, 2, 3, 4) (1, 2, 3, 4, 1, 2, 3, 4)
2 2
2
Le code suivant :
print("on a dit _non_ mutable !")
c[1] = 11
déclenche l’erreur suivante:
TypeError Traceback (most recent call last)
<ipython-input-10-b869b5ab11d8> in <module>
1 print("on a dit _non_ mutable !")
----> 2 c[1] = 11
TypeError: 'tuple' object does not support item assignment
7.4. Les listes list
#
list
: conteneur mutable de valeurs écrites entre [ ]
, séparées par des ,
, et indexées de 0 à len(lst)-1
[3, “bonjour”, 7, 1.32]
Les valeurs sont de type quelconque – non nécessairement unique.
En pratique, les listes python sont des structures de données sophistiquées, très souples d’utilisation.
7.4.1. Créer une liste#
Une liste peut être construite comme suit :
à partir d’une liste vide notée :
[]
explicitement (ou aussi dit en extension) avec des valeurs séparées par des
,
et entre[ ]
:[a]
,[a, b, c]
en compréhension :
[x for x in iterable]
avec le constructeur de type :
list()
oulist(iterable)
# une liste explicite
liste = [1, 2, 3, 4, 5, 6, 7]
sous_liste = liste[:3]
un_a_trois = (1, 2, 3)
autre = list(un_a_trois)
print(liste, sous_liste, autre)
print(un_a_trois == autre, autre == sous_liste)
[1, 2, 3, 4, 5, 6, 7] [1, 2, 3] [1, 2, 3]
False True
# Des listes en compréhension
# les carres des elements
les_carres = [x ** 2 for x in liste]
print(les_carres)
# Afficher les nombres pairs
print([x for x in liste if x % 2 == 0])
# Plus simple que filtrer, egalement :)
#Affiche les carres pairs (combinaison des deux)
print([x**2 for x in liste if x ** 2 % 2 == 0])
# ou print([x for x in [a ** 2 for a in liste] if x % 2 == 0])
[1, 4, 9, 16, 25, 36, 49]
[2, 4, 6]
[4, 16, 36]
7.4.2. Méthodes sur des listes#
Rappel : Une méthode est une fonction qui s’applique à une variable (un objet) avec une notation pointée – et non des parenthèses.
Compléments :
Une méthode peut être une fonction de plusieurs paramètres.
Le paramètre indispensable est celui mis devant le
.
de la notation pointée obligatoire.Les paramètres supplémentaires sont des arguments entre parenthèses en plus de la notation pointée.
Remarquer que les méthodes suivantes modifient la liste à laquelle elles s’appliquent.
Syntaxe |
Effet |
---|---|
|
ajoute la valeur |
|
supprime la dernière valeur de |
|
ajoute la séquence |
|
insère la valeur |
|
supprime la valeur |
|
supprime la valeur d’indice |
|
trie |
|
inverse |
Rmq.
Les 2 premières (
.append()
et.pop()
) sont les plus fondamentales.Les accès, modifications (ajout/suppression de valeur) et parcours d’une liste sont des opérations simples (et optimisées) grâce à ces méthodes.
Ainsi comme déjà indiqué, les listes en python sont des structures de données qui cachent une importante sophistication.
les_daltons = ["Jo", "Jack", "William", "Averell"]
print(les_daltons)
['Jo', 'Jack', 'William', 'Averell']
les_daltons[0] = "le méchant"
print(les_daltons)
['le méchant', 'Jack', 'William', 'Averell']
les_daltons.append("Ma Dalton")
print(les_daltons)
['le méchant', 'Jack', 'William', 'Averell', 'Ma Dalton']
# Voir les différences de comportement des 2 cas suivants
#chien_stupide = ("rantanplan",) # c'est un tuple
chien_stupide = ("rantanplan") # c'est une str
print(type(chien_stupide))
les_daltons.append(chien_stupide)
print(les_daltons)
<class 'str'>
['le méchant', 'Jack', 'William', 'Averell', 'Ma Dalton', 'rantanplan']
print("Attention :")
lst = list(chien_stupide)
print("lst:",lst)
Attention :
lst: ['r', 'a', 'n', 't', 'a', 'n', 'p', 'l', 'a', 'n']
print("on corrige :", les_daltons)
while les_daltons[-1] != "Averell":
les_daltons.pop()
print(les_daltons)
les_daltons.pop(0)
print(les_daltons)
on corrige : ['le méchant', 'Jack', 'William', 'Averell', 'Ma Dalton', 'rantanplan']
['le méchant', 'Jack', 'William', 'Averell']
['Jack', 'William', 'Averell']
les_daltons.reverse()
print(les_daltons)
les_daltons.sort()
print(les_daltons)
['Averell', 'William', 'Jack']
['Averell', 'Jack', 'William']
7.4.3. Affectation entre listes#
ATTENTION : l’affectation liste_destination = liste_source
ne crée pas une nouvelle liste_destination
.
L’effet de cette affectation est la création d’un nouveau nom de liste
liste_destination
qui désigne le même ensemble de valeurs en mémoire que celui désigné par l’autre nomliste_source
. On dit aussi que ces deux identifiants référencent le même espace en mémoire.Ainsi la modification de ces valeurs, qui est possible ici car les listes sont mutables, en utilisant un des deux noms (identifiant) source ou destination, agira indifféremment de l’identifiant utilisé : il n’y a qu’un seul ensemble de valeurs stockées en mémoire.
Il est bien sûr possible de dupliquer ces valeurs et les désigner effectivement avec deux identificateurs différents. Plusieurs solutions existent en python dont :
la fonction
copy
du modulecopy
l’affectation de tranches complètes.
Ce comportement, commun aux types composés mutables et qui peut surprendre, sera étudié en détail dans le chapitre “Fonctions : aspects avancés”.
Si besoin, l’utilisation de copy.copy
(importé du module copy
) sera la solution.
Cette particularité est en lien avec deux notions importantes suivantes qui sont classiques en programmation – au delà de python.
Notion d’effet de bord : la modification par un identifiant agit sur les valeurs “de” l’autre identifiant.
Notion de référence : l’identifiant d’une variable (mutable) est une référence vers un espace de mémoire.
Illustration avec python tutor
liste1 = [1, 1, 2]
liste2 = liste1
liste2[0] = 3
print(liste1 == liste2)
print(liste1, liste2)
print(id(liste1), id(liste2))
print()
True
[3, 1, 2] [3, 1, 2]
4359518912 4359518912
import copy
print("duplication avec copy.copy : ")
new_liste = copy.copy(liste1)
print(liste1, new_liste)
new_liste[0] = 999
print(new_liste == liste1)
print(liste1, new_liste)
print(id(liste1), id(new_liste))
duplication avec copy.copy :
[3, 1, 2] [3, 1, 2]
False
[3, 1, 2] [999, 1, 2]
4359518912 4359453120
print("duplication par affectation de tranche complète : ")
liste3 = liste1[:]
liste1[0] = -5
print(liste3 == liste1)
print(liste1, liste3)
print(id(liste1), id(liste3))
duplication par affectation de tranche complète :
False
[-5, 1, 2] [3, 1, 2]
4359518912 4354242368
7.5. Les dictionnaires dict
#
Les dictionnaires python sont des tableaux associatifs.
7.5.1. Tableau associatif#
Structure de données classique (hors python)
type de donnée composé
formé par les couples
clé -> valeur
la
clé
est unique dans le tableau associatif :clé
permet de désigner (d’atteindre)valeur
dans le dictionnaireDans un tableau associatif, la clé joue même rôle que l’indice dans un tableau.
cette
clé
n’induit pas un ordre sur les valeurs du tableau associatif – même si la clé est un entier !
7.5.2. Dictionnaire dict
en python#
Syntaxe.
des couples
clé : valeur
– bien noter la séparation avec:
écrits entre
{ }
etséparées par des
,
{clé1: val1, clé2: val2, ...}
Mutable, itérable mais non séquençable
Accès à la valeur :
un_dict[clé]
similaire à l’accès à la valeur dans une liste, dans une chaîne de caractères, …
h_lever_WE = {'samedi': 8, 'dimanche': 9 }
print(h_lever_WE['samedi'] > h_lever_WE['dimanche'])
False
Rmq.
Un dictionnaire n’est pas “ordonné par ses indices” :
Cette absence d’ordre justifie le caractère non séquençable de cette structure.
Différent des
str
,tuple
etlist
qui sont des conteneurs séquençables.
Les valeurs peuvent être de type quelconque
voir exemple suivant
L’accès
clé -> valeur
s’effectue en temps constanttrès court en pratique : technique de hachage
jour = 2
res = "toto"
message = ''
# ce dictionnaire utilise comme clés les valeurs des 3 variables précédentes
d = {jour:3, message:"bonjour", res:1.32}
print(d)
{2: 3, '': 'bonjour', 'toto': 1.32}
Opérateurs, fonctions et méthodes.
len()
: retourne le nombre de valeurs de l’argument
les_daltons = { "le_mechant": "Jo", '2': "Jack", 3: "William", "le_cretin": "Averell"}
print(len(les_daltons), les_daltons)
non = (les_daltons["le_mechant"] == les_daltons[3])
print("non:", non)
4 {'le_mechant': 'Jo', '2': 'Jack', 3: 'William', 'le_cretin': 'Averell'}
non: False
Rmq.
Les clés d’un dictionnaire sont de type quelconque et non constant.
En pratique, préférer des clés qui donnent un sens à la valeur correspondante et au dictionnaire.
Parcourir un dictionnaire
l’itération sur un contain :
for cle in un_dict
: appartenance d’une clépermet un parcours “naturel” selon les clés du dictionnaire.
méthode privilégiée pour parcourir tout un dictionnaire
On peut itérer sur les`valeurs du dictionnaire mais dans un ordre aléatoire.
iter(un_dict)
retourne un tuple (séquence) qui sert d’itérateur
print("Attention : l'itération simple suivante parcourt les clés")
for c in les_daltons:
print(c)
Attention : l'itération simple suivante parcourt les clés
le_mechant
2
3
le_cretin
print("Celle-ci aussi mais accède aux valeurs en parcourant les clés")
for c in les_daltons:
print(les_daltons[c])
Celle-ci aussi mais accède aux valeurs en parcourant les clés
Jo
Jack
William
Averell
Méthodes introspectives
.keys()
: fonction-méthode qui retourne la liste desclé
de l’argument.values()
: fonction-méthode qui retourne la liste desval
de l’argument.items()
: fonction-méthode qui retourne la liste des couplesclé:val
de l’argument
Rmq. Noter le pluriel des identifiants de ces méthodes.
print(les_daltons)
{'le_mechant': 'Jo', '2': 'Jack', 3: 'William', 'le_cretin': 'Averell'}
les_cles = les_daltons.keys()
print(les_cles)
#print(type(les_cles))
# type(les_cles[0]) produit TypeError: 'dict_keys' object is not subscriptable
dict_keys(['le_mechant', '2', 3, 'le_cretin'])
les_noms = les_daltons.values()
print(les_noms, type(les_noms))
dict_values(['Jo', 'Jack', 'William', 'Averell']) <class 'dict_values'>
tout = les_daltons.items()
print(tout, type(tout))
dict_items([('le_mechant', 'Jo'), ('2', 'Jack'), (3, 'William'), ('le_cretin', 'Averell')]) <class 'dict_items'>
Ce qui permet d’introduire d’autres modes de parcours d’un dictionnaire.
Rmq. C’est rendu possible grâce à la fonction iter()
.
for c in iter(les_daltons.keys()):
print(c)
print("--")
for v in iter(les_daltons.values()):
print(v)
print("--")
for v in iter(les_daltons.items()):
print(v, type(v))
le_mechant
2
3
le_cretin
--
Jo
Jack
William
Averell
--
('le_mechant', 'Jo') <class 'tuple'>
('2', 'Jack') <class 'tuple'>
(3, 'William') <class 'tuple'>
('le_cretin', 'Averell') <class 'tuple'>
Exercice. Quelle construction est inutile ?
Modifier un dictionnaire
.pop(cle)
: efface(cle:val)
et retourneval
del un_dic[cle]
détruit l’entréecle : val
du dictionnaireun_dic
.update(autre_dict)
: ajout ou maj avec unautre_dict
(et écrasement si clé commune).clear()
: efface le dictionnaire
print("mutable : ajout, retrait, modification")
les_daltons["la_maman"] = "Ma Dalton" #ajout
print(les_daltons)
mutable : ajout, retrait, modification
{'le_mechant': 'Jo', '2': 'Jack', 3: 'William', 'le_cretin': 'Averell', 'la_maman': 'Ma Dalton'}
del les_daltons["le_mechant"] #retrait
print(les_daltons)
{'2': 'Jack', 3: 'William', 'le_cretin': 'Averell', 'la_maman': 'Ma Dalton'}
print("le_mechant" in les_daltons, len(les_daltons))
print()
False 4
animaux_fideles = {"le_cretin":"Rantanplan", "la_classe": "Jolly Jumper"}
animaux_fideles.pop("la_classe") #retrait
print(animaux_fideles)
{'le_cretin': 'Rantanplan'}
les_daltons.update(animaux_fideles) # ajout-réduction de dictionnaires
print("changement de cretin : ", les_daltons)
changement de cretin : {'2': 'Jack', 3: 'William', 'le_cretin': 'Rantanplan', 'la_maman': 'Ma Dalton'}
7.6. Les chaînes de caractères str
#
Leur importance pratique mériterait presque un chapitre dédié. Bien relire cette section plusieurs fois.
str
(string) ou chaîne de caractères : type de donnée non mutable composé d’une suite ordonnée de caractères (séquence)
syntaxe : s’écrit indifféremment avec
' '
et" "
7.6.1. Opérations#
len()
, concaténation, répétition
Accès
par indexation :
s[i]
,s[0]
,s[-1]
par extraction de sous-chaînes ou tranches de
str
:s[i:j]
,s[:]
,s[i:]
,s[-2:]
,s[1:10:2]
hello = 'bonjour !'
print(hello[-1], len(hello))
s = hello[0:len(hello):2]
print(s, len(s))
! 9
bnor! 5
Modification
Une
str
est non mutableToute modification de sa valeur
créé une nouvelle
str
et détruit la précédente ;
ce qui justifie que la nouvelle
str
puisse être désignée par le même identifiant.Se souvenir des illustrations pythontutor déjà vues (copie de
list
, concaténation destr
).
s = 'abc'
print(s, id(s))
t = s
print(t, id(t))
s = s + 'def'
print(s, id(s))
abc 4301674064
abc 4301674064
abcdef 4354282672
7.6.2. Fonctions-méthodes#
Ces fonctions-méthodes spécifiques aux str
sont utiles en pratique.
Savoir qu’elles existent est suffisant dans le cadre de ce cours.
Les identifiants sont en général bien explicites.
On distingue surtout deux types de fonctions-méthodes.
Celles qui retournent un bool
:
.isupper()
,.istitle()
,.isalnum()
(comprendre is alpha and num),.isalpha()
,.isdigit()
,.isspace()
,.startswith()
,.endswith()
Celles qui modifient l’argument arg
:
la casse des caractères :
.upper()
,.lower()
,.capitalize()
,.swapcase()
la justification :
.expantabs(nb)
,.center(width)
,.ljust()
,.rjust()
,.zfill(width)
(comprendre zero fill),
ou aussi :
.replace(old, new, count)
oùcount
est le nombre de remplacements à effectuer,arg.join(cont)
concatène les chaînes du conteneurcont
en intercalantarg
,arg.split(seps)
partage la chaînearg
selonseps
et retourne le tout sous forme delist
destr
;très utile en pratique pour manipuler les mots d’un texte par exemple
arg.split()
(sans argumentseps
) partage la chaînearg
selon le (ou les) caractères espaces qu’elle comporte, la liste retournée ne comporte aucun mot vide.Plus de détail avec
help(str.split)
.
On finit en mentionnant cette dernière méthode de recherche :
arg.find(substr, deb, fin)
qui retourne l’indice desubstr
dansarg[deb,fin]
.
hello = 'bonjour !'
t0 = hello.split()
print(t0)
t = hello.split('o')
print(t)
print('z' in t, 'b' in t)
['bonjour', '!']
['b', 'nj', 'ur !']
False True
plus_fort = hello.upper()
print(hello, hello.isupper())
print(plus_fort, plus_fort.isupper(), plus_fort.isalpha(), '\n')
bonjour ! False
BONJOUR ! True False
liste_de_deux = plus_fort.split()
print(".split:", liste_de_deux, type(liste_de_deux), type(liste_de_deux[0]), '\n')
.split: ['BONJOUR', '!'] <class 'list'> <class 'str'>
liste_de_deux = '**!!**'.join(liste_de_deux)
print(".join:", liste_de_deux, type(liste_de_deux), '\n')
.join: BONJOUR**!!**! <class 'str'>
deux_fois_plus_fort = liste_de_deux.replace('!', '@@'+str(liste_de_deux)+'@@', 1)
print(".replace:", deux_fois_plus_fort)
.replace: BONJOUR**@@BONJOUR**!!**!@@!**!
deux_fois_plus_fort = liste_de_deux.replace('!', '@'+str(liste_de_deux)+'@', 2)
print(".replace:", deux_fois_plus_fort)
.replace: BONJOUR**@BONJOUR**!!**!@@BONJOUR**!!**!@**!
Rappel sur les fonctions-méthodes qui “modifient” une str
Le caractère non mutable des
str
n’est pas mis en défaut par les méthodes de modificationCelles-ci créent de nouvelles
str
hello = 'bonjour !'
print(id(hello))
bjr = hello
print(id(bjr))
plus_fort = hello.upper()
print(id(plus_fort))
hello = plus_fort
print(id(hello))
4359573168
4359573168
4359505392
4359505392
7.6.3. Les caractères spéciaux ou caractères/séquences d’échappement#
Caractère d’échappement : “backslash” \
Caractères spéciaux ou séquences d’échappement : l’association du “backslash” \
et de certains symboles (ou suite de symboles) a un sens particulier dans une chaîne de caractères.
Cela permet par exemple d’interpréter différemment les symboles '
ou "
ou
.
Quelques exemples (\(\star\)) à connaitre :
\'
ou\"
ou\\
ou\/
: les symboles'
ou"
ou\
ou/
\b
: backspace\t
\v
: tabulation horizontale ou verticale\n
: new line saut de ligne\f
: form feed saut de page\r
: retour en début de ligne
Remarques.
Cette notion se retrouve dans d’autres langages de programmation : C, …
On parle aussi de séquence d’échappement mais retenons que l’association
\
suivie d’un caractère est “comptée” comme un seul caractère : un caractère spécial.
Exemples.
s1 = 'bonjour'
s2 = '\tbonjour'
print(s1)
print(s2)
bonjour
bonjour
print('s1:', len(s1))
print('s2:', len(s2))
print('[', s1[0], ']', sep='')
print('[', s2[0], ']', sep='')
s1: 7
s2: 8
[b]
[ ]
Comment utiliser le symbole \
sans qu’il s’évalue comme l’annonce d’une séquence d’échappement ?
avec lui même !
ainsi
\\
s’évalue comme le caractère\
et
\\n
comme la chaîne de 2 caractères non évalués\n
(piégeux !).
print(len('\n'), len('\\n'))
1 2
print("\\ texte avec '\\' au début et à la fin \\")
print()
print("Bo\njour")
print("Bo\\njour")
print("Bo\\n\b\bnjour")
\ texte avec '\' au début et à la fin \
Bo
jour
Bo\njour
Bo\nnjour
Ainsi le \
évite d’évaluer “normalement” le symbole qui le suit.
Rmq.
Dans l’exemple suivant, expliquer pourquoi va
(aux lignes affichées 2 et 4) n’est pas à la même colonne entre le premier et le second print
?
print('Bonjour \"mon ami\" ! \n \t \t Ca va ?')
print('Bonjour \"mon ami\" ! \n \t \t Ca va ? \r Comment')
Bonjour "mon ami" !
Ca va ?
Bonjour "mon ami" !
Ca va ?
Comment
Réponse :
Analyse du premier
print()
: on s’intéresse à l’interprétation par leprint()
de la chaîne de caractère argument.\n
et\t
sont resp. les symboles du saut à la ligne et de la tabulationla tabulation est interprétée dans mon environnement comme 7 (caractères) espaces
lors de l’affichage, la chaîne
" \t \t Ca va ?"
à afficher à partir du\n
est donc interprétée comme :1 espace
7 espaces (1 tabulation)
1 espace
7 espaces (1 tabulation)
2 espaces
les 7 caractères
Ca va ?
.
Soit donc au total 25 caractères affichés sur la seconde ligne (après le
\n
).
Analyse de second
print()
: on va expliciter la valeur de la chaîne de caractères sans se soucier dans un premier temps de son interprétation à l’affichage, cad. lors de l’exécution duprint()
.\t
est une chaîne de caractères de longueur 1, cad. un caractère :essayer
type('\t')
etlen('\t')
à partir du
\n
, la chaîne de caractères\t \t Ca va ?
est de longueur 13essayer
len(' \t \t Ca va ?')
\r
revient en début de ligne dans cette chaîne de caractèrescad. juste après le
\n
et donc “recule” (la position d’entrée) de 13 caractères sur cette ligne
Comment
est un chaîne de caractères de longueur 8elle est placée aux 8 premiers caractères à partir de la position du curseur, cad. du début de la ligne
et ainsi écrase les 8 premiers caractères de la chaîne
\t \t Ca va ?
ces 8 premiers caractères sont :
\t \t Ca
donc la chaîne de caractètes à afficher devient, à partir du
\n
:Comment va ?
on peut vérifier que
len(" Comment va ?")
retourne 13.
L’affichage obtenu est conforme à l’analyse.
print('[\t]')
tab = '[' + 'x'*7 + ']' # sept x entre crochets
print(tab)
print()
print('Bonjour \"collègue\" ! \n \t \t Ca va ?')
s = '.'*25
print(s)
print('Bonjour \"collègue\" ! \n \t \t Ca va ? \r Comment')
s = '.'*13
print(s)
[ ]
[xxxxxxx]
Bonjour "collègue" !
Ca va ?
.........................
Bonjour "collègue" !
Ca va ?
Comment
.............
print(len(' Comment va ?'))
13
7.7. Les ensembles set
et frozenset
#
set
(ensemble) : type composé de valeurs uniques écrites entre { }
et séparées par des ,
.
{3, “bonjour”, 7, 1.32}
set
: ensemble mutable(\(\star\))
frozenset
: ensemble non mutable
La fonction
set()
permet de créer un ensemble à partir d’un tuple ou d’une chaîne de caractèresEt de façon similaire, la fonction
frozenset()
…
bof = {'b', 'o', 'f'}
voyelles = set('aeiouy')
aie = set('aie')
print(voyelles, len(voyelles))
{'o', 'u', 'y', 'e', 'i', 'a'} 6
f_set = frozenset("non mutable")
print(f_set, len(f_set))
frozenset({'o', 'n', 'l', 'u', ' ', 'e', 'm', 'b', 'a', 't'}) 10
print("aie :", aie)
print("voyelles :", voyelles)
aie : {'i', 'a', 'e'}
voyelles : {'o', 'u', 'y', 'e', 'i', 'a'}
print('a' in voyelles)
print('a' in bof)
True
False
Opérations :
opérations ensemblistes (des maths) :
in
etnot in
: appartenance ou non|
: union&
: intersection-
: différence (habituellement notée\
:a - b
= { x \(\in\)a
| x \(\notin\)b
})^
: différence symétrique (complémentaire de l’intersection)<=
ou<
: inclusion ensembliste, resp. est un sous-ensemble de vs. est inclus strictement dans;a < b
==a <= b
anda != b
(et de même :>=
>
)
Rappel. Si besoin : différence et différence symétrique
print("union :", voyelles | aie)
print("autre union :", voyelles | bof)
print("intersection :", voyelles & aie)
print("difference : ", aie - voyelles, bof - voyelles)
print("difference symétrique: ", aie ^ voyelles)
print("inclusions :", aie < voyelles, aie <= voyelles, aie >= voyelles, bof < voyelles)
#print(aie[1]) # déclenche une erreur : l'accès indexé n'est pas possible dans un ensemble
union : {'o', 'u', 'y', 'e', 'i', 'a'}
autre union : {'o', 'u', 'y', 'f', 'e', 'b', 'i', 'a'}
intersection : {'i', 'a', 'e'}
difference : set() {'b', 'f'}
difference symétrique: {'o', 'u', 'y'}
inclusions : True True False False
Rmq. On n’accède pas à la valeur d’un set
avec la notation indicée [ ]
.
7.8. Spécifier des fonctions avec des types composés#
Les fonctions peuvent manipuler des arguments de type composé.
On a déjà vu beaucoup d’exemples avec les tableaux. Ces derniers ayant été représentés en python par le type (composé) liste, on sait expliciter le type de tels paramètres à l’aide de :
list
depuis python > 3.9List
(avec une majuscule) du moduletyping
pour les versions antérieures.
On va étendre cette approche aux type composés vus dans ce chapitre.
Depuis python 3.9, il est possible de paramètrer les types prédéfinis avec des [ ]
de façon similaire à celle décrite dans la section 2 de cette partie.
Il n’est donc plus nécessaire de passer par les types “avec majuscule” de typing
.
7.8.1. Paramètres de type composé prédéfini#
Les types composés prédéfinis de python sont renvoyés par la fonction type()
(appliquée à une variable).
list
dict
tuple
set
str
unTuple = (1,2,3)
uneListe = [1, 'a', 1.3]
unDictionnaire = { "le_mechant":"Jo", '2':"Jack", 3:"William", "le_cretin": "Averell"}
unEnsemble = {'b', 'o', 'f'}
uneChaineDeCaracteres = "bof"
for v in (unTuple, uneListe, unDictionnaire, unEnsemble, uneChaineDeCaracteres):
print(type(v))
<class 'tuple'>
<class 'list'>
<class 'dict'>
<class 'set'>
<class 'str'>
Solution minimale. On peut donc utiliser ces types prédéfinis pour spécifier le type des paramètres d’une fonction.
Par la suite, on utilisera au moins cette solution minimale pour toutes les spécifications de fonction avec des paramètres de type composé.
def nb_caracteres(s : str) -> int:
return len(s)
def nb_entrees(d : dict) -> int:
return len(d)
def card(s : set) -> int:
return len(s)
(\(\star\)) Surcharge de fonctions.
2 fonctions d’identifiant identiques mais de signature différentes grâce au typage de ses paramètres formels. On appelle aussi cette homonymie de la surcharge de fonctions.
def nb_val(l : list) -> int:
return len(s)
def nb_val(t : tuple) -> int:
return len(s)
print(nb_caracteres(uneChaineDeCaracteres))
print(nb_entrees(unDictionnaire))
print(card(unEnsemble))
for v in (uneListe, unTuple):
print(nb_val(v))
3
4
3
13
13
Rmq. Dans l’exemple précédent, on a 2 fonctions d’identifiant identique nb_val()
mais de signatures différentes grâce au typage de leurs paramètres formels.
On a par exemple déjà signalé cette caractéristique avec les différents sens du symbole +
selon qu’il s’applique à des entiers, des flottants, des chaînes de caractères, …
7.8.2. (\(\star\)) Mieux expliciter le type composé#
Comme on l’a fait pour mieux expliciter les tableaux, on peut expliciter “l’intérieur” d’un type composé, cad. le type de ses composants.
Par exemple, le type d’un tableau 2D de valeurs entières (type composé qui regroupe des valeurs de même type) a été spécifié (depuis python>3.9) par :
list[list[int]]
On a ainsi explicité :
la dimension du tableau : ici 2 et la représentation comme une liste de listes
le type de ses valeurs : ici des entiers, ie. des
int
python > 3.9
On peut directement faire de même pour les types composés :
list
,dict
,tuple
,set
Ces types permettent d’expliciter “l’intérieur” des arguments de type composé. La syntaxe s’appuie sur les crochets [ ]
déjà rencontrés pour les tableaux-listes.
def nb_entrees(d : dict[str, str]) -> int:
return len(d)
def card(s : set[str]) -> int:
return len(s)
d0 = {'1':"a", '2':"b"}
res = nb_entrees(d0)
print(d0, res)
s0 = set(d0)
res = card(s0)
print(s0, res)
{'1': 'a', '2': 'b'} 2
{'1', '2'} 2
(\(\star\)) Union
pour la surcharge de fonctions
Le module typing
définit Union
:
Union[int, float]
est un paramètre de typeint
oufloat
.Il convient par exemple pour écrire une seule version de la fonction
nb_val()
.
# exemple d'Union pour la surcharge
from typing import Union
def nb_val(cont : Union[list[int], tuple[int]]) -> int:
return len(cont)
unTuple = (1,2,3)
uneListe = [1, 'a', 1.3]
unDictionnaire = { "le_mechant":"Jo", '2':"Jack", 3:"William", "le_cretin": "Averell"}
unEnsemble = {'b', 'o', 'f'}
uneChaineDeCaracteres = "bof"
print(nb_entrees(unDictionnaire))
print(card(unEnsemble))
for v in (uneListe, unTuple):
print(nb_val(v))
# autre exmple avec Union
def maxListe( l : list[Union[int, float]]) -> int:
m = l[0]
for i in range(len(l)):
if l[i]> m:
m = l[i]
return m
resI = maxListe([1, 0, 1, 2, 2, 2])
resF = maxListe([1.0, 1.2, 2.2])
print(resI, resF)
4
3
3
3
2 2.2
python < 3.9.
Le module typing
définit des types similaires mais qui commencent avec une majuscule :
List
,Dict
,Tuple
,Set
Ainsi par exemple :
Tuple[int, float, str]
est un tuple triplet composé d’un entier, d’un flottant et d’une chaîne de caractères.Dict[str, int]
est un dictionnaire de couples clés:valeur de typesstr:int
from typing import List
def maxTab2D( t : List[List[int]], d1 : int, d2 : int) -> int:
m = t[0][0]
for i in range(d1):
for j in range(d2):
if t[i][j] > m:
m = t[i][j]
return m
print(maxTab2D([[1,0],[1,2]], 2, 2))
2
Consulter la documentation pour d’autres constructions plus avancées et très utiles.
7.9. Synthèse#
Conteneurs non mutables :
str
,tuple
(etfrozenset
)Conteneurs mutables :
list
,dict
,set
Création, accès aux valeurs, parcours et modification
Des fonctions (pour les non mutables) et des fonction-méthodes
Attention : la modification de non mutable cache une recopie en mémoire
Attention : bien que très simples à utiliser, certaines fonctions-méthodes sont d’un coût algorithmique élevé (complexité non constante)
Quels mutables choisir ?
les
list
pour des tableaux 1D de valeurs de type qcq et où le traitement demande de la souplesseles
dict
pour des clésstr
(\(\star\)) mais aussi les
ndarray
pour du calcul numérique type algèbre linéaire ou les tableaux multi-dimensionnels de valeurs numériques – voir cette annexe.
La spécification d’arguments de type composé avec le module
typing
ou plus directement en python>=3.9
7.9.1. Avoir les idées claires#
Distinguer les caractéristiques des structures de données : tuple, liste, dictionnaire, ensemble, et chaîne de caractères – aussi appelés type composé ou conteneur en python :
pour une représentation,un stockage des données et des traitements adaptés au problème
pour effectuer l’accès aux valeurs, le parcours (séquences, itérateur), et la modification ou la copie (mutabilité) de ces SD
(\(\star\)) Connaître les complexités de ces traitements
7.9.2. Savoir faire#
Connaitre la syntaxe python de définition et manipulation de ces SD
Créer des listes et dictionnaires en compréhension
Connaître les méthodes (prédéfinies) utiles au traitement, en particulier des chaînes de caractères
Définir des spécifications de fonctions avec un ou des paramètres de type composé