Vous pouvez me contacter via Facebook pour questions & suggestions :
Page Facebook relative à mon site
Un conteneur dans le jargon Python désigne un objet qui contient une collection d'éléments donc une variable qui
contient plusieurs valeurs.
Vous connaissez déjà les différents types de conteneurs : listes, chaines, tuples, dictionnaires, ensembles.
Il est possible qu'à ce stade de votre apprentissage de Python, vous soyez un peu perdu, que vous fassiez de nombreuses confusions.
C'est normal il faut un peu de temps pour décanter toute l'information contenue dans les précédents chapitre.
Je profite aussi de ce chapitre pour évoquer les expressions régulières en Python.
Si vous connaissez JavaScript & PHP vous connaissez cet outil qu'on retrouve dans tous les langages.
Considérez ce chapitre comme une pause dans votre apprentissage de Python.
Vous apprendrez peu de choses mais ce sera l'occasion d'y voir plus clair, de dégager une typologie des conteneurs, d'acquérir
une méthodologie pour retenir sans peine et aussi pour trouver rapidement l'information.
Cependant je présente aussi deux nouveaux conteneurs :
Sachez que l'on peut attribuer à chaque type de conteneur des qualificatifs
et qu'à partir de ces propriétés vous saurez ce qu'on peut faire (ou pas) pour chaque type de conteneur.
Je prends un premier exemple ; on dit qu'un tuple est un conteneur séquentiel mas non mutable.
Exemple :
>>> montuple = 5,4,3 >>> montuple[0] 5 >>> montuple.index(5) 0 >>> del montuple[0] TypeError: 'tuple' object doesn't support item deletion
Et si je vous dis qu'une liste est un objet séquentiel et mutable. Qu'en déduisez vous ???
Tous les conteneurs sont des structures itérables : peuvent être parcourues par une boucle.
Pour tous les conteneurs on peut appliquer la fonction générique len() qui retourne la longeur du conteneur.
On peut appliquer à n'importe quel type de conteneur l'opérateur d'appartenance "in" (ou "not in").
Une séquence est un conteneur ordonné et indexable.
Les listes, les chaines, les tuples et les "range" sont des séquences.
Par contre les ensembles et les dictionnaires ne sont pas des séquences.
>>> maliste = list(range(10)) >>> len(maliste) 10 >>> maliste[-1] 9 >>> 8 in maliste True >>> monset = set(range(10)) >>> len(monset) 10 >>> 7 in monset True >>> monset[-1] ... TypeError: 'set' object is not subscriptable >>> maliste = list(range(10) ... ) >>> len(maliste) 10 >>> maliste[-1] 9 >>> 8 in maliste True >>> maliste.index(0) 0 >>> monset = set(range(10)) >>> len(monset) 10 >>> 7 in monset True >>> monset[-1] ... TypeError: 'set' object is not subscriptable >>> monset.index(0) ... AttributeError: 'set' object has no attribute 'index'
Un "set" n'est pas "subscriptable" (indexable) ; on ne peut accéder à un élément via son indice. On ne peut non plus utiliser la méthode index()
Il existe des conteneurs que l'on peut modifier après leur création et d'autres non modifiables après leur création.
Un tuple, une chaine & un 'range' sont immuables : on ne peut pas modifier leur contenu.
Les autres conteneurs (liste, dictionnaire, ensemble) sont mutables : on peut modifier leur contenu (ajout, suppression,
insertion).
>>> maliste = list(range(10) >>> del maliste[0] >>> maliste [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> machaine ="Bonjour mes amis" >>>machaine[0] ='b' ... TypeError: 'str' object does not support item assignment
J'arrive à supprimer un élément d'une liste mais je n'arrive pas à remplacer un caractère d'une chaine.
Chaque conteneur de type mutable propose différentes méthodes pour ajouter, insérer, supprimer des éléments.
Les conteneurs sont des structures itérables : peuvent être parcourues par une boucle.
Dans une boucle basique : for ... in série l'indice n'apparait pas.
Par contre l'index(ou indice) apparait dans la boucle for ...in enumerate(nomConteneur)
Quelles méthodes puis-je employer pour chaque type de conteneur ?
Inutile d'apprendre par coeur les méthodes pour chaque type de collection ; il suffit de faire appel à l'aide Python.
Pour connaitre les méthodes d'une classe il suffit de taper dans la console de Python :
dir(nomClasse) OU help(nomClasse)
Vous constaterez que certaines méthodes sont partagées par plusieurs types de conteneurs.
Ainsi on retrouve la méthode index( chez toutes les structures séquentielles.
A l'exception des méthodes magiques (évoquées plus tard) la classe tuple ne propose que deux méthodes ...
Ce qui est assez logique puisqu'il s'agit d'une structure immuable.
Revenons à la commande dir(nomClasse) qui offre l'avantage de donner une information synthétique.
Pour chaque type d'objet je n'affiche que les méthodes standards.
>>> dir(range) 'count', 'index', 'start', 'step', 'stop' >>> impairs =range(1,11,2) >>> del impairs[-1] ... TypeError: 'range' object doesn't support item deletion
Un objet 'range' est une séquence non mutable.
>>> dir(list) 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'
Une liste est une séquence mutable.
Vous avez parfois le choix entre une fonction "built in" ou une méthode de classe pour certains traitements.
Si vous voulez trier une liste, vous avez le choix entre la méthode .sort() de la classe "list" et la fonction "built in" sorted()
>>> maliste =list(range(11,1,-2)) >>> maliste [11, 9, 7, 5, 3] >>> sorted(maliste) [3, 5, 7, 9, 11] >>> maliste [11, 9, 7, 5, 3] >>> maliste.sort() >>> maliste [3, 5, 7, 9, 11] >>> machaine ="bonjour" >>> sorted(machaine) ['b', 'j', 'n', 'o', 'o', 'r', 'u'] >>> montuple = 5,4,3,2 >>> sorted(montuple) [2, 3, 4, 5]
La fonction générique sorted() se contente d'afficher la liste triée mais ne modifie pas la liste d'origine.
On peut appliquer la fonction sorted() à d'autres types de conteneurs.
Par contre la méthode sort() modifie l'ordre des éléments sur place ; l'ordre d'origine est perdu.
Cette fonction retourne alors une liste !
>>> pairs = list(range(0,16,2)) >>> pairs [0, 2, 4, 6, 8, 10, 12, 14] >>> sorted(pairs,reverse = True) [14, 12, 10, 8, 6, 4, 2, 0] >>> pairs [0, 2, 4, 6, 8, 10, 12, 14] >>> pairs.sort(reverse = True) >>> pairs [14, 12, 10, 8, 6, 4, 2, 0] >>>
Pour trier effectivement une liste par ordre "descending" (ou décroissant) il faut utiliser l'argument reverse = True avec la méthode .sort() .
Pour certaines manipulations sur les conteneurs, il n'y a pas de méthodes ; il faut utiliser les fonctions génériques. Vous pensez déjà aux fonctions type() et len() mais il y en a d'autres ...
>>> unset = set(range(1,11,2))
>>> unset
{1, 3, 5, 7, 9}
>>> max(unset)
9
>>> min(unset)
1
>>> sum(unset)
25
>>> unechaine ="Salut"
>>> len(unechaine)
5
>>> max(unechaine)
'u'
>>> min(unechaine)
'S'
>>>
>>> sum(unechaine)
...
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>> del (unset)
>>> del (unechaine)
Ci-dessus j'ai appliqué les fonctions génériques len(), max(),min() & sum() à un objet "set" et un objet "str". Les méthodes de classe pour effectuer de tels traitements n'existent pas !
La commande del nomConteneur permet de supprimer un conteneur. Ne confondez pas avec la méthode .clear() qui existe pour les séries mutables et qui vide le conteneur (sans le supprimer).
L'image est grivoise mais le discours qui suit est des plus sérieux.
Dans le paragraphe ci-dessous j'évoque des traitements sur les chaines qui n'ont pas encore été évoqués.
Pour rechercher un caractère (ou plusieurs) dans une chaine, vous pensez à index() mais il existe aussi find(), rindex() et rfind(), startswith(), endswidth() .
Vous pouvez aussi faire une recherche en utilisant une RE (Regular Expression), une expression régulière.
>>> machaine ="avec cesar"
>>> machaine.index('a')
0
>>> machaine.index('y')
ValueError: substring not found
>>> machaine.find('y')
-1
>>> machaine.rindex('a')
8
>>> machaine.rfind('a')
8
>>> machaine.rindex('y')
ValueError: substring not found
>>> machaine.rfind('y')
-1
>>> "y" in machaine
False
>>> "y" not in machaine
True
>>> "a" in machaine
True
>>> chaine1 ='bonjour'
>>> chaine1.startswith('bon')
True
>>> chaine1.endswith('soir')
False
>>>
>>> machaine_cryptee = machaine.replace('a','1').replace('e','2')
>>> machaine_cryptee
'1v2 c2s1r'
>>> machaine
'avec cesar'
Pour effectuer une recherche sur une chaine vous pouvez aussi la comparer à une expression régulière via la méthode
search() du module re ("re" comme regular expression).
Voir la deuxième partie de ce chapitre.
>>> machaine = " bon jour "
>>> machaine.strip()
'bon jour'
>>> machaine.lstrip()
'bon jour '
>>> machaine.rstrip()
' bon jour'
>>> machaine.strip().replace(" ","")
'bonjour'
>>> machaine # commande 5
' bon jour '
>>> machaine2 = machaine.strip().replace(" ","") #commande 6
>>> machaine2
'bonjour'
>>> machaine = machaine.strip().replace(" ","") #commande 7
>>> machaine
'bonjour'
>>>
lstrip() efface les espaces en début de chaine, rstrip() efface les espaces en fin de chaine ;
strip() équivaut à lstrip().rstrip().
Pour effacer les espaces en milieu de chaine il pouvez utiliser .replace() de la
façon suivante : replace(" " ."") (remplacer espace par rien).
Un objet string est immuable ; vous ne pouvez pas modifier le contenu d'une chaine.
L'astuce consiste donc à créer une nouvelle variable : machaine2 = machaine.strip() ... OU écraser le contenu d'origine
de la variable : machaine = machaine.strip() ...
Les méthodes strip(), lstrip() & lstrip() sans arguments effacent les espaces. Mais vous pouvez argumenter ces méthodes. pour vous débarasser d'autres caractères.
>>> machaine =" * Bonjour * - "
>>> machaine = machaine.lstrip(" *-").rstrip(" * -")
>>> machaine
'Bonjour'
>>>
Pour effacer des caractères parasites en milieu de chaine l'astuce consiste à utiliser replace() avec en deuxième argument une chaine vide.
>>> chainebizarre ="B*- on*-jour"
>>> chainecorrigee = chainebizarre.replace(" ","").replace("*","").replace("-","")
>>> chainecorrigee
'Bonjour'
Le 'slicing" a déjà été évoqué. Une simple expression entre crochets signifie beaucoup de choses.
Aussi la compréhension de cette expression n'est pas toujours facile.
Rappel : l'indice peut être une valeur négative afin d'extraire à partir de la fin de la chaine.
Rappelez vous, dans le chapitre 6 j'ai évoqué le code de César : un système de cryptage simple reposant sur un décalage de n lettres.
>>> alphabet ="abcdefghijklmnopqrstuvwxyz" >>> n=2 >>> alphabet_2n =alphabet[n:] + alphabet[:n] >>> alphabet_2n 'cdefghijklmnopqrstuvwxyzab'
Grâce au "slicing" j'obtiens la table de conversion ("a" devient "c" , "b" devient "d" ... "z" devient "b") dans l'hypothèse d'un décalage de 2 (valeur de n).
Voilà une problèmatique que vous allez rencontrer souvent en programmation Python.
En effet la fonction input() retourne toujours une donnée de type 'str' même si vous n'avez saisi qu'une suite de chiffres ...
>>> chaine ="1245" >>> type(chaine) class 'str' >>> somme = chaine *2 >>> somme '12451245' >>> int(chaine) 1245 >>> chaine2 ="1245.55" >>> int(chaine2) ValueError: invalid literal for int() with base 10: '1245.55' >>> nombre = eval(chaine2) >>> type(nombre) class 'float' >>> nombre 1245.55
somme = chaine * 2 : dans certains langages (JS) il y aurait conversion du contenu de "chaine" en numérique puis multiplication par 2.
La fonction int(chaine) convertit une chaine en entier si celle-ci ne contient qu'une suite de chiffres mais "plante" si
le format de la chaine est celle d'un flottant (le séparateur décimal dans la chaine).
La fonction eval(chaine) convertit une chaine numérique en flottant.
La fonction float(chaine) fait la même chose !
Une liste est mutable ; on peut modifier son contenu initial.
C'est pour cette raison que les méthodes de liste sont nombreuses et beaucoup visent à modifier le contenu de la séquence.
>>> maliste = list(range(10)) >>> maliste [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> maliste[0:] = range(2,12) >>> maliste [2, 3, 4, 5, 6, 7, 8, 9, 10, 11] >>> del(maliste[0:]) >>> maliste []
Dans la liste "maliste" chaque élément a été remplacé par son ancienne valeur + 2
Puis la liste est vidée (mais pas supprimée).
Il existe aussi les méthodes extend() pour ajout N éléments en fin de liste et insert(position, valeur) pour insérer un élément au milieu d'une liste.
Ci-dessous je vous montre que vous pouvez modifier le contenu d'une liste sans recourir à des méthodes de liste.
>>> maliste =list(range(10)) >>> maliste [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> maliste[0] = 11 >>> maliste [11, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> del maliste[-1] >>> maliste [11, 1, 2, 3, 4, 5, 6, 7, 8]
J'ai modifié le contenu de la liste sans utiliser de méthodes de liste mais uniquement via des accès indexés.
Un dictionnaire (tableau associatif dans d'autres langages) est un conteneur qui comprend non pas des éléments mais des paires "clé : valeur" appelées items.
>>> repertoire = {"marius":"0601010101", "émile":"0602020202",
"bernard": "0603030303","damien":"0604040404"}
>>> type(repertoire)
class 'dict'
>>> repertoire.get("damien")
'0604040404'
>>> repertoire.get("louise")
>>> repertoire["françois"] ="0605050505"
>>> rperteoire.pop("marius")
'0601010101'
>>> repertoire["émile"] = "0701010101"
>>> repertoire.clear()
>>> repertoire
{}
>>> ajout = {'alain': '0601010101', 'bernard':'0602020202'}
>>> repertoire.update(ajout)
>>> repertoire
{'alain': '0601010101', 'bernard': '0602020202'}
Pour créer un dictionnaire on utilise le format JSON (accolades, clés sous forme de chaine).
Un frozenset est un set non mutable !
Donc si vous voulez un série immuable et sans doublons optez ce type de conteneur.
>>> dir(frozenset)
'copy', 'difference', 'intersection', 'isdisjoint', 'issubset', 'issuperset', 'symmetric_difference', 'union'
>>> monfrozenset = frozenset(range(10))
>>> monfrozenset
frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
>>> monfrozenset.add(10)
...
AttributeError: 'frozenset' object has no attribute 'add'
>>> type(monfrozenset)
class 'frozenset'
>>> frozenset2 = frozenset({5,5,3,3})
>>> frozenset2
frozenset({3, 5})
On peut procéder aux opérations union et intersection sur un frozenset.
En programmation, un tableau est une collection d'éléments du même type.
Or rien n'interdit dans une liste Python de mélanger des entiers, des chaines, des flottants, etc.
Le module array de Python permet de créer de véritables tableaux indicés au sens informatique du terme c'est à dire des séries où tous les éléments ont le même type.
>>> from array import array
>>> b = array('i', range(10))
>>> b
array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b[0] = 10 # changer une valeur
>>> b
array('i', [10, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b.append(10.5)
...
TypeError: integer argument expected, got float
>>> b.append(11)
>>> b
array('i', [10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11])
>>> type(b)
class 'array.array'
>>> for i,element in enumerate(b):
... print(i, element)
...
0 10
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 11
>>> sorted(b)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> del(b[0:])
>>> b
array('i')
>>>
b = array('i', range(10)) : création d'un tableau indicé ne comprenant que des entiers.
Le constructeur "array" est obligatoire ainsi que le type de contenu :
J'ai tenté d'ajouté un nombre décimal au tableau : échec !
Cet ajout aurait été possible si la variable "b" était une simple liste.
Puis j'ai utilisé l'accès indexé pour modifier un élément puis pour vider tout le tableau.
Je vous montre aussi qu'un "array" est itérable.
Les RE ne sont pas propres à Python ; c'est un outil commun à de nombreux langages. Et de plus la syntaxe est quasi identique dans ces différents langages.
Dans le cadre d'une saisie une RE permet un contrôle de saisie efficace (remplace de nombreux tests).
Une RE permet aussi de recherche des occurences dans une chaine pour éventuellement effectuer un remplacement.
Une RE est une chaine donc entre guillemets.
Pour une coïncidence exacte avec la chaine à laquelle elle est comparée, elle doit commencer par ^ et se terminer par $.
On peut utiliser des caractères ordinaires.
Nous cherchons si le mot contient "bonjour" ; la RE sera : "bonjour"
Nous cherchons si le mot commence par "abc" ; la RE sera : "^abc"
Nous cherchons si le mot commence par "isme" ; la RE sera: "isme$"
Nous cherchons si le mot coïncide strictement à "bonjour" ; la RE sera : "^bonjour$".
Nous cherchons si le mot contient "salut" quelque soit la casse ; la RE sera "salut" mais il faudra
rajouter un argument à la méthode search() du module re ; voir ci-dessous.
# expression_reguliere.py
import re
format ="chat"
while True:
mot_saisi = input("saisir un mot ou 999:")
if mot_saisi =="999" :
break
if re.search(format,mot_saisi,re.IGNORECASE):
print(f" {mot_saisi} contient la RE {format}")
else:
print(f" {mot_saisi} ne contient pas la RE {format}")
Pour créer et manipuler des expressions régulières il faut importer le module re (comme regular expression).
...re.search(format,mot_saisi,re.IGNORECASE) : la méthode search() comprend un troisième argument qui précise qu'il ne faut pas tenir compte de la casse.
saisir un mot ou 999:chaton chaton contient l'expression chat saisir un mot ou 999:CHATEAU CHATEAU contient l'expression chat saisir un mot ou 999:cHateAU cHateAU contient l'expression chat saisir un mot ou 999:chapeau chapeau ne contient pas l'expression chat saisir un mot ou 999:
CHATEAU & cHateAU contiennent "chat" si on ne tient pas compte de la casse.
Une RE complexe peut être une suite de paires "classe-quantificateur".
On reconnait une classe car elle est entre crochets.
Une classe est l'énumération de caractères autorisés sous forme d'une liste ou d'un intervalle.
Des exemples sont préférables à un long discours.
À la place de classes on peut utiliser certains raccourcis.
Un quantificateur suit une classe et indique le nombre de caractères de la classe autorisés.
Les symboles *,+, . , ? , ^, $ sont utilisés dans la syntaxe de construction d'une RE.
Si vous voulez les utiliser dans une classe en tant que caractères autorisés, il faut alors les échapper.
Il s'agit d'une amélioration du programme "cercle.py". Voir chapite X.
Les fonctions sont identiques à la version précédente.
Ce programma autorise la saisie d'un nombre décimal.
#programme principal
import math
import re
from turtle import *
format = "^[0-9\.]{1,}$"
rayon =""
while not re.search(format,rayon):
rayon = input("saisir le rayon sous forme d'un nombre entier ou décimal : ")
rayon =eval(rayon) #conversion chaine en numérique
circonference, aire = cercle1(rayon) # appel fonction cercle1
print ("circonférence du cercle : " , circonference)
print ("aire du cercle : " , aire)
cercle2(rayon) #appel procédure cercle2
...
format ="^[0-9\.]{1,}$" : seuls les chiffres et le point sont autorisés dans la saisie ; au moins 1 de ces caractères
Il y a ici une classe et un quantificateur.
saisir le rayon sous forme d'un nombre entier ou décimal : 12cm saisir le rayon sous forme d'un nombre entier ou décimal : 12,5 saisir le rayon sous forme d'un nombre entier ou décimal : 12.5 circonférence du cercle : 78.53981633974483 aire du cercle : 490.8738521234052
Seuls les chiffres et le point sont autorisés par la RE.
Donc si la chaine saisie via INPUT contient autre chose, on reste dans la boucle de saisie.
Cette fois la RE est relativement complexe.
# expression_reguliere2.py
import re
format_mail ="^\w{1,}@\w{2,}\.[A-z]{2,4}$"
while True:
email_saisi = input("saisir une adresse mail valide ")
if email_saisi =="999" :
break
if re.search(format_mail,email_saisi):
print(f" {email_saisi} est une adresse mail valide")
else:
print(f" {email_saisi} est une adresse mail invalide")
Observez la RE ; j'ai utilisé la classe abrégée \w qui équivaut à [A-z0-9_]
saisir une adresse mail valide : darchefreefr darchefreefr est une adresse mail invalide saisir une adresse mail valide : darche@free darche@free est une adresse mail invalide saisir une adresse mail valide : darche@free.fr darche@free.fr est une adresse mail valide