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'autres objets.
Vous connaissez déjà les différents types de "containers" : listes, chaines, tuples, dictionnaires, ensembles ainsi
que les "range".
Il est possible qu'à ce stade de votre apprentissage de Python, vous soyez un peu perdu et que vous vous posiez légitimmment
certaines questions.
Dans ce chapitre je vais traiter deux types de conteneurs qui n'ont pas encore été évoqués : les frozensets et les
arrays. Ces derniers ne sont pas à confondre avec les "numpy.array" ...
Pourquoi une telle diversité de conteneurs ?
Quels traitements pour chaque type de conteneur ?
Sachez que l'on peut attribuer à chaque type de conteneur des qualificatifs.
Et qu'à partir des qualité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.
Autre exemple : on dit qu'un ensemble (ou 'set') est un objet non séquentiel et mutable.
Un "set" à une autre qualité : les doublons dans cette série sont impossibles.
Et si je vous dis qu'une liste est un objet séquentiel 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 >>> 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] Traceback (most recent call last): File "", line 1, in 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 index. On ne peut non plus faire de recherche d'index à partir de la valeur : .index() non disponible.
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).
>>> del maliste[0] >>> maliste [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> machaine ="Bonjour mes amis" >>> del machaine[-1] ... TypeError: 'str' object doesn't support item deletion >>>del machaine >>> machaine ... NameError: name 'machaine' is not defined
J'arrive à supprimer un élément d'une liste mais je n'arrive pas à supprimer un caractère d'une chaine.
Chaque conteneur de type mutable propose différentes méthodes pour ajouter, insérer, supprimer des éléments.
J'ai supprime "machaine" avec la fonction del(nomConteneur).
Les conteneurs sont des séquences 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)
>>> impairs =range(1,11,2) >>> impairs range(1, 11, 2) #affichage d'un objet range >>> for indice, ele in enumerate(impairs): ... print(indice,ele) ... 0 1 1 3 2 5 3 7 4 9
Donc emploi de trois fonctions natives (ou génériques) de Python : list(), tuple(), set()
>>> liste = list(range(1,10,2)) >>> liste [1, 3, 5, 7, 9] >>> for index, valeur in enumerate(liste): ... print(index, valeur) ... 0 1 1 3 2 5 3 7 4 9
J'espère que vous notez bien l'apport de for ... in enumerate() par rapport à for ... in
Je peux appliquer for ... enumerate à une chaine ou à un tuple ou un ensemble.
>>> chaine ="Bonjour" >>> for index,car in enumerate(chaine): ... print(index,car) ... 0 B 1 o 2 n 3 j 4 o 5 u 6 r
>>> tuple = tuple(range(5)) >>> tuple (0, 1, 2, 3, 4) >>> for index,ele in enumerate(tuple) : ... print(index,ele) ... 0 0 1 1 2 2 3 3 4 4
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)
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 série 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
d'où les nombreuses méthodes qui modifient le contenu de la liste : .append(), .clear(), .insert(), .pop(), .remove(),
.sort(), .reverse()
Tous les conteneurs mutables proposent la méthode .clear()
Tous les conteneurs séquentiels proposent la méthode .index()
>>> dir(set) 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update' >>> monset {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} >>> monset.discard(5) >>> monset {0, 1, 2, 3, 4, 6, 7, 8, 9} >>> del monset[0] ... TypeError: 'set' object doesn't support item deletion
un ensemble est une collection d'éléments non ordonnés ; c'est donc un conteneur non séquentiel mais mutable.
Pour supprimer un élément on doit utiliser la méthode .discard(élement). On ne peut utiliser la fonction
nomConteneur.del[index]
>>> dir(tuple) 'count', 'index'
Un tuple est une séquence non mutable d'où l'absence de méthodes pour modifier son contenu.
>>> dir(dict) 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values' >>>
Un dictionnaire est un conteneur non séquentiel ; c'est un conteneur mutable.
>>> dir(str) 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill' >>> >>> machaine 'Bonjour mes amis' >>> machaine.upper() 'BONJOUR MES AMIS' >>> machaine 'Bonjour mes amis' >>> machaine = machaine.upper() # on écrase l'ancien contenu de la variable "machaine" >>> machaine 'BONJOUR MES AMIS'
Une chaine est une séquence non mutable.
La liste des méthode disponible est impressionnante alors qu'il s'agit d'une série non mutable ...
Mais ces méthodes se contentent de modifier l'affichage de la chaine à moins d'écraser l'ancien contenu d'origine
de la variable chaine.
Comme je disais plus haut, vous avez parfois le choix entre une fonction générique 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 native 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]
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 dans la liste.
Cette fonction affiche toujours en retour 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() .
>>> machaine ="CBAcba" >>> chaine_triee = sorted(machaine) >>> machaine 'CBAcba' >>> chaine_triee ['A', 'B', 'C', 'a', 'b', 'c'] >>> type(chaine_triee) class 'list' >>> machaine = sorted(machaine) >>> machaine ['A', 'B', 'C', 'a', 'b', 'c']
La fonction sorted() affiche la chaine sous forme d'une liste.
C'est une façon de produire une liste à partir d'une chaine si vous affecter le tri à une variable OU de
convertir une chaine en liste si vous écrasez l'ancien contenu.
Les lettres majuscules apparaissent en tête car leur code ASCII est plus petit que les minuscules.
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 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 ce paragraphe j'évoque des traitements sur les chaines qui n'ont pas encore été évoqués.
Pour réchercher un caractère (ou plusieurs) dans une chaine, vous pensez à index() mais il existe aussi find(), rindex() et rfind(), startswith(), endswidth() .
>>> 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'
>>> 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.
Sachez que l'indice peut être une valeur négative afin d'extraire à partir de la fin (ou droite 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.
Grâce au découpage 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 que des chiffres.
>>> chaine ="1245" >>> 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
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.
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 chaque élément remplacé par son ancienne valeur + 2
Puis la liste est vidée.
>>> maliste =[2,3,2,5,7,9] >>> maliste.append(11) >>> maliste [2, 3, 2, 5, 7, 9, 11] >>> maliste.reverse() >>> maliste [11, 9, 7, 5, 2, 3, 2] >>> maliste.sort() >>> maliste [2, 2, 3, 5, 7, 9, 11] >>> maliste.remove(11) >>> maliste.pop(0) >>> maliste [2, 3, 5, 7, 9] >>> maliste.clear() >>> maliste []
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é la valeur du premier élément puis j'ai supprimé le dernier élément.
On ne peut pas vraiment comparer une liste Python avec un tableau indicé dans d'autres langages.
En effet rien n'interdit dans une liste de mélanger des entiers avec des flottants ou des booléens ou des chaines.
Or en Informatique un tableau indicé est une série de données de même natures (uniquement des entiers, par exemple).
Vous verrez plus loin dans ce chapitre que le module array permet de créer de véritables tableaux indicés en Python.
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)>>> repertoire.get("damien") '0604040404' >>> repertoire.get("louise") >>> repertoire["françois"] ="0605050505" >>> rperteoire.pop("marius") '0601010101' >>> repertoire["émile"] = "0701010101" >>> repertoire.popitem() ('françois', '0605050505') >>> repertoire.clear() >>> repertoire {} >>> ajout = {'alain': '0601010101', 'bernard':'0602020202'} >>> type(ajout) >>> repertoire.update(ajout) >>> repertoire {'alain': '0601010101', 'bernard': '0602020202'} >>> repertoire_copie = repertoire >>> repertoire_copie {'alain': '0601010101', 'bernard': '0602020202'}
Un frozenset est un set non mutable !
>>> 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' >>> dir(frozenset) ... 'copy', 'difference', 'intersection', 'isdisjoint', 'issubset', 'issuperset', 'symmetric_difference', 'union'] >>>
On peut procéder aux opérations union et intersection sur un frozenset.
Les méthodes .add(), .discard(), .clear() ne sont pas , et c'est logique, disponibles.
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 dont 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 !
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.
Dans le chapitre précédent je vous ai présenté la bibliothèque numpy qui permet de créer des tableaux. Et maintenant je vous parle des tableaux créés avec le module array. Il y a de quoi s'y perdre.
En fait la bibliothèque numpy permet de créer des tableaux au sens mathématique du terme c'est à dire des matrices.
Alors que les "array" du module du même nom n'ont aucune vocation mathématique ; ce sont simplement des
listes typées ; on ne peut faire de calculs matriciels sur des "array.array".
Par ailleurs les "numpy.array" sont immuables alors que "array.array" sont mutables.
J'espère qu'à l'issue de ce chapitre vous avez désormais les idées plus claires.
Vous avez aussi découvert deux types de conteneurs que je n'avais pas encore évoqué : les frozensets et les arrays.
Si vous savez qu'un conteneur est séquentiel (ou pas), mutable (ou pas) vous en déduirez
les traitements autorisés et ceux qui ne le sont pas.
Prenons l'exemple d'une chaine : c'est une séquence non mutable donc les méthodes .index() & .count() sont disponibles et on
peut pratiquer l'accès indexé. Par contre aucune méthode de modifification n'est disponible.
Autre exemple la liste qui est un séquence mutable. Donc de nombreuses méthodes de modification sont présentes ; .append(),
.insert(), .pop(), .remove(), .extend(), .clear()
N'importe quel conteneur (mutable ou immuable) peut être supprimé avec la commande del(nomConteneur).