Mis à jour le vendredi 17 novembre 2017
  • 40 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Les listes et tuples (2/2)

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Les listes sont très utilisées en Python. Elles sont liées à pas mal de fonctionnalités, dont certaines plutôt complexes. Aussi ai-je préféré scinder l'approche des listes en deux chapitres. Vous allez voir dans celui-ci quelques fonctionnalités qui ne s'appliquent qu'aux listes et aux tuples, et qui pourront vous être extrêmement utiles. Je vous conseille donc, avant tout, d'être bien à l'aise avec les listes et leur création, parcours, édition, suppression…

D'autre part, comme pour la plupart des sujets abordés, je ne peux faire un tour d'horizon exhaustif de toutes les fonctionnalités de chaque objet présenté. Je vous invite donc à lire la documentation, en tapanthelp(list), pour accéder à une liste exhaustive des méthodes.

C'est parti !

Entre chaînes et listes

Nous allons voir un moyen de transformer des chaînes en listes et réciproquement.

Il est assez surprenant, de prime abord, qu'une conversion soit possible entre ces deux types qui sont tout de même assez différents. Mais comme on va le voir, il ne s'agit pas réellement d'une conversion. Il va être difficile de démontrer l'utilité de cette fonctionnalité tout de suite, mieux valent quelques exemples.

Des chaînes aux listes

Pour « convertir » une chaîne en liste, on va utiliser une méthode de chaîne nomméesplit(« éclater » en anglais). Cette méthode prend un paramètre qui est une autre chaîne, souvent d'un seul caractère, définissant comment on va découper notre chaîne initiale.

C'est un peu compliqué et cela paraît très tordu… mais regardez plutôt :

>>> ma_chaine = "Bonjour à tous"
>>> ma_chaine.split(" ")
['Bonjour', 'à', 'tous']
>>>

On passe en paramètre de la méthodesplitune chaîne contenant un unique espace. La méthode renvoie une liste contenant les trois mots de notre petite phrase. Chaque mot se trouve dans une case de la liste.

C'est assez simple en fait : quand on appelle la méthodesplit, celle-ci découpe la chaîne en fonction du paramètre donné. Ici la première case de la liste va donc du début de la chaîne au premier espace (non inclus), la deuxième case va du premier espace au second, et ainsi de suite jusqu'à la fin de la chaîne.

Sachez quesplitpossède un paramètre par défaut, un code qui représente les espaces, les tabulations et les sauts de ligne. Donc vous pouvez très bien fairema_chaine.split(), cela revient ici au même.

Des listes aux chaînes

Voyons l'inverse à présent, c'est-à-dire si on a une liste contenant plusieurs chaînes de caractères que l'on souhaite fusionner en une seule. On utilise la méthode de chaînejoin(« joindre » en anglais). Sa syntaxe est un peu surprenante :

>>> ma_liste = ['Bonjour', 'à', 'tous']
>>> " ".join(ma_liste)
'Bonjour à tous'
>>>

En paramètre de la méthodejoin, on passe la liste des chaînes que l'on souhaite « ressouder ». La méthode va travailler sur l'objet qui l'appelle, ici une chaîne de caractères contenant un unique espace. Elle va insérer cette chaîne entre chaque paire de chaînes de la liste, ce qui au final nous donne la chaîne de départ, « Bonjour à tous ».

N'aurait-il pas été plus simple ou plus logique de faire une méthode de liste, prenant en paramètre la chaîne faisant la jonction ?

Ce choix est en effet contesté mais, pour ma part, je ne trancherai pas. Le fait est que c'est cette méthode qui a été choisie et, avec un peu d'habitude, on arrive à bien lire le résultat obtenu. D'ailleurs, nous allons voir comment appliquer concrètement ces deux méthodes.

Une application pratique

Admettons que nous ayons un nombre flottant dont nous souhaitons afficher la partie entière et les trois premières décimales uniquement de la partie flottante. Autrement dit, si on a un nombre flottant tel que « 3.999999999999998 », on souhaite obtenir comme résultat « 3.999 ». D'ailleurs, ce serait plus joli si on remplaçait le point décimal par la virgule, à laquelle nous sommes plus habitués.

Là encore, je vous invite à essayer de faire ce petit exercice par vous-même. On part du principe que la valeur de retour de la fonction chargée de la pseudo-conversion est une chaîne de caractères. Voici quelques exemples d'utilisation de la fonction que vous devriez coder :

>>> afficher_flottant(3.99999999999998)
'3,999'
>>> afficher_flottant(1.5)
'1,5'
>>>

Voici la correction que je vous propose :

def afficher_flottant(flottant):
    """Fonction prenant en paramètre un flottant et renvoyant une chaîne de caractères représentant la troncature de ce nombre. La partie flottante doit avoir une longueur maximum de 3 caractères.

    De plus, on va remplacer le point décimal par la virgule"""
    
    if type(flottant) is not float:
        raise TypeError("Le paramètre attendu doit être un flottant")
    flottant = str(flottant)
    partie_entiere, partie_flottante = flottant.split(".")
    # La partie entière n'est pas à modifier
    # Seule la partie flottante doit être tronquée
    return ",".join([partie_entiere, partie_flottante[:3]])

En s'assurant que le type passé en paramètre est bien un flottant, on garantit qu'il n'y aura pas d'erreur lors du fractionnement de la chaîne. On est sûr qu'il y aura forcément une partie entière et une partie flottante séparées par un point, même si la partie flottante n'est constituée que d'un 0. Si vous n'y êtes pas arrivés par vous-même, étudiez bien cette solution, elle n'est pas forcément évidente au premier coup d'œil. On fait intervenir un certain nombre de mécanismes que vous avez vus il y a peu, tâchez de bien les comprendre.

Les listes et paramètres de fonctions

Nous allons droit vers une fonctionnalité des plus intéressantes, qui fait une partie de la puissance de Python. Nous allons étudier un cas assez particulier avant de généraliser : les fonctions dont le nombre de paramètres est inconnu.

Notez malgré tout que ce point est assez délicat. Si vous n'arrivez pas bien à le comprendre, laissez cette section de côté, cela ne vous pénalisera pas.

Les fonctions dont on ne connaît pas à l'avance le nombre de paramètres

Vous devriez tout de suite penser à la fonctionprint: on lui passe une liste de paramètres qu'elle va afficher, dans l'ordre où ils sont placés, séparés par un espace (ou tout autre délimiteur choisi).

Vous n'allez peut-être pas trouver d'applications de cette fonctionnalité dans l'immédiat mais, tôt ou tard, cela arrivera. La syntaxe est tellement simple que c'en est déconcertant :

def fonction(*parametres):

On place une étoile*devant le nom du paramètre qui accueillera la liste des arguments. Voyons plus précisément comment cela se présente :

>>> def fonction_inconnue(*parametres):
...     """Test d'une fonction pouvant être appelée avec un nombre variable de paramètres"""
...     
...     print("J'ai reçu : {}.".format(parametres))
... 
>>> fonction_inconnue() # On appelle la fonction sans paramètre
J'ai reçu : ().
>>> fonction_inconnue(33)
J'ai reçu : (33,).
>>> fonction_inconnue('a', 'e', 'f')
J'ai reçu : ('a', 'e', 'f').
>>> var = 3.5
>>> fonction_inconnue(var, [4], "...")
J'ai reçu : (3.5, [4], '...').
>>>

Je pense que cela suffit. Comme vous le voyez, on peut appeler lafonction_inconnueavec un nombre indéterminé de paramètres, allant de 0 à l'infini (enfin, théoriquement). Le fait de préciser une étoile*devant le nom du paramètre fait que Python va placer tous les paramètres de la fonction dans un tuple, que l'on peut ensuite traiter comme on le souhaite.

Et les paramètres nommés dans l'histoire ? Comment sont-ils insérés dans le tuple ?

Ils ne le sont pas. Si vous tapezfonction_inconnue(couleur="rouge"), vous allez avoir une erreur :fonction_inconnue() got an unexpected keyword argument 'couleur'. Nous verrons au prochain chapitre comment capturer ces paramètres nommés.

Vous pouvez bien entendu définir une fonction avec plusieurs paramètres qui doivent être fournis quoi qu'il arrive, suivis d'une liste de paramètres variables :

def fonction_inconnue(nom, prenom, *commentaires):

Dans cet exemple de définition de fonction, vous devez impérativement préciser un nom et un prénom, et ensuite vous mettez ce que vous voulez en commentaire, aucun paramètre, un, deux… ce que vous voulez.

Au fond, cela est évident. Vous ne pouvez avoir une définition de fonction commedef fonction_inconnue(*parametres, nom, prenom). En revanche, si vous souhaitez avoir des paramètres nommés, il faut les mettre après cette liste. Les paramètres nommés sont un peu une exception puisqu'ils ne figureront de toute façon pas dans le tuple obtenu. Voyons par exemple la définition de la fonctionprint:

print(value, ..., sep=' ', end='\n', file=sys.stdout)

Ne nous occupons pas du dernier paramètre. Il définit le descripteur vers lequelprintenvoie ses données ; par défaut, c'est l'écran.

D'où viennent ces points de suspension dans les paramètres ?

En fait, il s'agit d'un affichage un peu plus agréable. Si on veut réellement avoir la définition en code Python, on retombera plutôt sur :

def print(*values, sep=' ', end='\n', file=sys.stdout):

Petit exercice : faire une fonctionafficheridentique àprint, c'est-à-dire prenant un nombre indéterminé de paramètres, les affichant en les séparant à l'aide du paramètre nommésepet terminant l'affichage par la variablefin. Notre fonctionafficherne comptera pas de paramètrefile. En outre, elle devra passer parprintpour afficher (on ne connaît pas encore d'autres façons de faire). La seule contrainte est que l'appel àprintne doit compter qu'un seul paramètre non nommé. Autrement dit, avant l'appel àprint, la chaîne devra avoir été déjà formatée, prête à l'affichage.

Pour que ce soit plus clair, je vous mets la définition de la fonction, ainsi que ladocstringque j'ai écrite :

def afficher(*parametres, sep=' ', fin='\n'):
    """Fonction chargée de reproduire le comportement de print.
    
    Elle doit finir par faire appel à print pour afficher le résultat.
    Mais les paramètres devront déjà avoir été formatés. 
    On doit passer à print une unique chaîne, en lui spécifiant de ne rien mettre à la fin :

    print(chaine, end='')"""
    
    # Les paramètres sont sous la forme d'un tuple
    # Or on a besoin de les convertir
    # Mais on ne peut pas modifier un tuple
    # On a plusieurs possibilités, ici je choisis de convertir le tuple en liste
    parametres = list(parametres)
    # On va commencer par convertir toutes les valeurs en chaîne
    # Sinon on va avoir quelques problèmes lors du join
    for i, parametre in enumerate(parametres):
        parametres[i] = str(parametre)
    # La liste des paramètres ne contient plus que des chaînes de caractères
    # À présent on va constituer la chaîne finale
    chaine = sep.join(parametres)
    # On ajoute le paramètre fin à la fin de la chaîne
    chaine += fin
    # On affiche l'ensemble
    print(chaine, end='')

J'espère que ce n'était pas trop difficile et que, si vous avez fait des erreurs, vous avez pu les comprendre.

Ce n'est pas du tout grave si vous avez réussi à coder cette fonction d'une manière différente. En programmation, il n'y a pas qu'une solution, il y a des solutions.

Transformer une liste en paramètres de fonction

C'est peut-être un peu moins fréquent mais vous devez connaître ce mécanisme puisqu'il complète parfaitement le premier. Si vous avez un tuple ou une liste contenant des paramètres qui doivent être passés à une fonction, vous pouvez très simplement les transformer en paramètres lors de l'appel. Le seul problème c'est que, côté démonstration, je me vois un peu limité.

>>> liste_des_parametres = [1, 4, 9, 16, 25, 36]
>>> print(*liste_des_parametres)
1 4 9 16 25 36
>>>

Ce n'est pas bien spectaculaire et pourtant c'est une fonctionnalité très puissante du langage. Là, on a une liste contenant des paramètres et on la transforme en une liste de paramètres de la fonctionprint. Donc, au lieu d'afficher la liste proprement dite, on affiche tous les nombres, séparés par des espaces. C'est exactement comme si vous aviez faitprint(1, 4, 9, 16, 25, 36).

Mais quel intérêt ? Cela ne change pas grand-chose et il est rare que l'on capture les paramètres d'une fonction dans une liste, non ?

Oui je vous l'accorde. Ici l'intérêt ne saute pas aux yeux. Mais un peu plus tard, vous pourrez tomber sur des applications où les fonctions sont utilisées sans savoir quels paramètres elles attendent réellement. Si on ne connaît pas la fonction que l'on appelle, c'est très pratique. Là encore, vous découvrirez cela dans les chapitres suivants ou dans certains projets. Essayez de garder à l'esprit ce mécanisme de transformation.

On utilise une étoile * dans les deux cas. Si c'est dans une définition de fonction, cela signifie que les paramètres fournis non attendus lors de l'appel seront capturés dans la variable, sous la forme d'un tuple. Si c'est dans un appel de fonction, au contraire, cela signifie que la variable sera décomposée en plusieurs paramètres envoyés à la fonction.

J'espère que vous êtes encore en forme, on attaque le point que je considère comme le plus dur de ce chapitre, mais aussi le plus intéressant. Gardez les yeux ouverts !

Les compréhensions de liste

Les compréhensions de liste (« list comprehensions » en anglais) sont un moyen de filtrer ou modifier une liste très simplement. La syntaxe est déconcertante au début mais vous allez voir que c'est très puissant.

Parcours simple

Les compréhensions de liste permettent de parcourir une liste en en renvoyant une seconde, modifiée ou filtrée. Pour l'instant, nous allons voir une simple modification.

>>> liste_origine = [0, 1, 2, 3, 4, 5]
>>> [nb * nb for nb in liste_origine]
[0, 1, 4, 9, 16, 25]
>>>

Étudions un peu la ligne 2 de ce code. Comme vous avez pu le deviner, elle signifie en langage plus conventionnel « Mettre au carré tous les nombres contenus dans la liste d'origine ». Nous trouvons dans l'ordre, entre les crochets qui sont les délimiteurs d'une instruction de compréhension de liste :

  • nb * nb: la valeur de retour. Pour l'instant, on ne sait pas ce qu'est la variablenb, on sait juste qu'il faut la mettre au carré. Notez qu'on aurait pu écrirenb**2, cela revient au même.

  • for nb in liste_origine: voilà d'où vient notre variablenb. On reconnaît la syntaxe d'une bouclefor, sauf qu'on n'est pas habitué à la voir sous cette forme.

Quand Python interprète cette ligne, il va parcourir la liste d'origine et mettre chaque élément de la liste au carré. Il renvoie ensuite le résultat obtenu, sous la forme d'une liste qui est de la même longueur que celle d'origine. On peut naturellement capturer cette nouvelle liste dans une variable.

Filtrage avec un branchement conditionnel

On peut aussi filtrer une liste de cette façon :

>>> liste_origine = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> [nb for nb in liste_origine if nb % 2 == 0]
[2, 4, 6, 8, 10]
>>>

On rajoute à la fin de l'instruction une condition qui va déterminer quelles valeurs seront transférées dans la nouvelle liste. Ici, on ne transfère que les valeurs paires. Au final, on se retrouve donc avec une liste deux fois plus petite que celle d'origine.

Mélangeons un peu tout cela

Il est possible de filtrer et modifier une liste assez simplement. Par exemple, on a une liste contenant les quantités de fruits stockées pour un magasin (je ne suis pas sectaire, vous pouvez prendre des hamburgers si vous préférez). Chaque semaine, le magasin va prendre dans le stock une certaine quantité de chaque fruit, pour la mettre en vente. À ce moment, le stock de chaque fruit diminue naturellement. Inutile, en conséquence, de garder les fruits qu'on n'a plus en stock.

Je vais un peu reformuler. On va avoir une liste simple, qui contiendra des entiers, précisant la quantité de chaque fruit (c'est abstrait, les fruits ne sont pas précisés). On va faire une compréhension de liste pour diminuer d'une quantité donnée toutes les valeurs de cette liste, et on en profite pour retirer celles qui sont inférieures ou égales à 0.

>>> qtt_a_retirer = 7 # On retire chaque semaine 7 fruits de chaque sorte
>>> fruits_stockes = [15, 3, 18, 21] # Par exemple 15 pommes, 3 melons...
>>> [nb_fruits-qtt_a_retirer for nb_fruits in fruits_stockes if nb_fruits>qtt_a_retirer]
[8, 11, 14]
>>>

Comme vous le voyez, le fruit de quantité 3 n'a pas survécu à cette semaine d'achats. Bien sûr, cet exemple n'est pas complet : on n'a aucun moyen fiable d'associer les nombres restants aux fruits. Mais vous avez un exemple de filtrage et modification d'une liste.

Prenez bien le temps de regarder ces exemples : au début, la syntaxe des compréhensions de liste n'est pas forcément simple. Faites des essais, c'est aussi le meilleur moyen de comprendre.

Nouvelle application concrète

De nouveau, c'est à vous de travailler.

Nous allons en gros reprendre l'exemple précédent, en le modifiant un peu pour qu'il soit plus cohérent. Nous travaillons toujours avec des fruits sauf que, cette fois, nous allons associer un nom de fruit à la quantité restant en magasin. Nous verrons au prochain chapitre comment le faire avec des dictionnaires ; pour l'instant on va se contenter de listes :

>>> inventaire = [
...     ("pommes", 22),
...     ("melons", 4),
...     ("poires", 18),
...     ("fraises", 76),
...     ("prunes", 51),
... ]
>>>

Recopiez cette liste. Elle contient des tuples, contenant chacun un couple : le nom du fruit et sa quantité en magasin.

Votre mission est de trier cette liste en fonction de la quantité de chaque fruit. Autrement dit, on doit obtenir quelque chose de similaire à :

[
    ("fraises", 76),
    ("prunes", 51),
    ("pommes", 22),
    ("poires", 18),
    ("melons", 4),
]

Pour ceux qui n'ont pas eu la curiosité de regarder dans la documentation des listes, je signale à votre attention la méthodesortqui permet de trier une liste. Vous pouvez également utiliser la fonctionsortedqui prend en paramètre la liste à trier (ce n'est pas une méthode de liste, faites attention).sortedrenvoie la liste triée sans modifier la liste d'origine, ce qui peut être utile dans certaines circonstances, précisément celle-ci. À vous de voir, vous pouvez y arriver par les deux méthodes.

Bien entendu, essayez de faire cet exercice en utilisant les compréhensions de liste.

Je vous donne juste un petit indice : vous ne pouvez trier la liste comme cela, il faut l'inverser (autrement dit, placer la quantité avant le nom du fruit) pour pouvoir ensuite la trier par quantité. Un chapitre entier est consacré au tri en Python, vous verrez d'autres moyens pour trier plus efficacement. Mais en attendant, essayez de travailler avec ce que vous savez faire.

Voici la correction que je vous propose :

# On change le sens de l'inventaire, la quantité avant le nom
inventaire_inverse = [(qtt, nom_fruit) for nom_fruit,qtt in inventaire]
# On n'a plus qu'à trier dans l'ordre décroissant l'inventaire inversé
# On reconstitue l'inventaire trié
inventaire = [(nom_fruit, qtt) for qtt,nom_fruit in sorted(inventaire_inverse, \
    reverse=True)]

Cela marche et le traitement a été fait en deux lignes.

Vous pouvez trier l'inventaire inversé avant la reconstitution, si vous trouvez cela plus compréhensible. Il faut privilégier la lisibilité du code.

# On change le sens de l'inventaire, la quantité avant le nom
inventaire_inverse = [(qtt, nom_fruit) for nom_fruit,qtt in inventaire]
# On trie l'inventaire inversé dans l'ordre décroissant
inventaire_inverse.sort(reverse=True)
# Et on reconstitue l'inventaire
inventaire = [(nom_fruit, qtt) for qtt,nom_fruit in inventaire_inverse]

Faites des essais, entraînez-vous, vous en aurez sans doute besoin, la syntaxe n'est pas très simple au début. Et évitez de tomber dans l'extrême aussi : certaines opérations ne sont pas faisables avec les compréhensions de listes ou alors elles sont trop condensées pour être facilement compréhensibles. Dans l'exemple précédent, on aurait très bien pu remplacer nos deux à trois lignes d'instructions par une seule, mais cela aurait été dur à lire. Ne sacrifiez pas la lisibilité pour le simple plaisir de raccourcir votre code.

En résumé

  • On peut découper une chaîne en fonction d'un séparateur en utilisant la méthodesplitde la chaîne.

  • On peut joindre une liste contenant des chaînes de caractères en utilisant la méthode de chaînejoin. Cette méthode doit être appelée sur le séparateur.

  • On peut créer des fonctions attendant un nombre inconnu de paramètres grâce à la syntaxedef fonction_inconnue(*parametres):(les paramètres passés se retrouvent dans le tuple parametres).

  • Les compréhensions de listes permettent de parcourir et filtrer une séquence en en renvoyant une nouvelle.

  • La syntaxe pour effectuer un filtrage est la suivante :nouvelle_squence = [element for element in ancienne_squence if condition].

Exemple de certificat de réussite
Exemple de certificat de réussite