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, d'avoir une conversion 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 d'une réelle conversion. Il va être difficile de démontrer l'utilité 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ée split ( « découper » en anglais). Cette méthode prend un paramètre, une autre chaîne, souvent d'un caractère, qui définit comment on va découper notre chaîne initiale.
C'est un peu compliqué et ça paraît très tordu... mais regardez plutôt :
Code : Python Console1
2
3
4
>>> ma_chaine = "Bonjour à tous"
>>> ma_chaine.split(" ")
['Bonjour', 'à', 'tous']
>>>
On passe en paramètre de la méthode split une chaîne contenant une unique espace. La méthode retourne 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éthode split, elle va découper la chaîne en fonction du paramètre donné, ici la première case de la liste va être du début jusqu'à la première espace (non incluse), puis de la première espace à la seconde, ainsi de suite jusqu'à la fin de la chaîne.
Sachez que split possède un paramètre par défaut, un code qui définit les espaces, les tabulations et les sauts de ligne. Donc vous pouvez très bien faire ma_chaine.split() , ça revient ici au même.
Des listes aux chaînes
Voyons l'inverse à présent : si on a une liste contenant des chaînes de caractères que nous souhaitons rassembler en une seule. On utilise la méthode de chaîne join (joindre en anglais
). Sa syntaxe d'utilisation est un peu surprenante :
Code : Python Console1
2
3
4
>>> ma_liste = ['Bonjour', 'à', 'tous']
>>> " ".join(ma_liste)
'Bonjour à tous'
>>>
En paramètre de la méthode join, 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 une unique espace. Elle va insérer cette chaîne entre chaque chaîne 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 je ne trancherai pas pour ma part. 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 avons un nombre flottant dont nous souhaitons afficher la partie entière et la partie flottante, mais uniquement les trois premières décimales. Autrement dit, si on a un nombre flottant tel que 3.999999999999998, on souhaite avoir 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 :
Code : Python Console1
2
3
4
5
>>> afficher_flottant(3.99999999999998)
'3,999'
>>> afficher_flottant(1.5)
'1,5'
>>>
Voici la correction que je vous propose :
Secret Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
Code : Python 1
2
3
4
5
6
7
8
9
10
11
12
13
14
def afficher_flottant(flottant):
"""Fonction prenant en paramètre un flottant et retournant une chaîne
de caractères avec ce nombre tronqué. La partie flottante
doit être d'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 assure qu'il n'y aura pas d'erreurs 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é par vous-même, regardez bien cette solution, elle n'est pas forcément simple au premier coup d'oeil. 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 avec une liste inconnue de paramètres.
Notez malgré tout que ce point est assez délicat. Si vous n'arrivez pas bien à le comprendre, laissez cette sous-partie de côté, ça ne vous pénalisera pas
.
Les fonctions dont on ne connaît pas le nombre de paramètres à l'avance
Vous devriez tout de suite penser à la fonction print : on lui passe une liste de paramètres que la fonction va afficher, dans l'ordre où ils sont placés, séparés par une espace (ou tout autre délimiteur choisi).
Vous n'allez peut-être pas trouver sur le moment d'applications de cette fonctionnalité, mais tôt ou tard cela arrivera. La syntaxe est tellement simple que c'en est déconcertant :
Code : Python1
def fonction(*parametres):
On place une étoile * devant le nom du paramètre qui accueillera la liste des paramètres. Voyons un peu plus précisément comment cela se présente :
Code : Python Console 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> def fonction_inconnue(*parametres):
... """Test d'une fonction pouvant être appelée avec un nombre variable
... de paramètres.
... """
... print("J'ai reçu : {0}.".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 notre fonction_inconnue avec un nombre indéterminé de paramètres, 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 entrez fonction_inconnue(couleur="rouge") , vous allez avoir une erreur : fonction_inconnue() got an unexpected keyword argument 'couleur' . Nous verrons dans le chapitre suivant comment capturer ces paramètres nommés.
Vous pouvez bien entendu définir une fonction avec plusieurs paramètres que l'on doit rentrer quoi qu'il arrive, et ensuite une liste de paramètres variables :
Code : Python1
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
.
Si on définit une liste variable de paramètres, elle doit se trouver après la liste des paramètres standards.
Cela est évident au fond. Vous ne pouvez avoir une définition de fonction comme def 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 fonction print.
Citation
print(value, ..., sep=' ', end='\n', file=sys.stdout)
Ne nous occupons pas du dernier paramètre. Il définit le descripteur vers lequel print envoie 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 :
Code : Python1
def print(*values, sep=' ', end='\n', file=sys.stdout):
Petit exercice : faire une fonction afficher identique à print, c'est-à-dire prenant un nombre indéterminé de paramètres, les affichant en les séparant à l'aide du paramètre nommé sep et terminant l'affichage par la variable fin. Notre fonction afficher ne comptera pas de paramètre file. En outre, elle devra passer par print pour afficher (on ne connaît pas encore d'autres façons de faire). La seule contrainte est que l'appel à print ne 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 la docstring que j'ai écrite :
Code : Python1
2
3
4
5
6
7
8
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='')
"""
Voici la solution que je vous propose :
Secret Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
Code : Python 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 = 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. Au contraire, tant que vous comprenez la solution que je propose
.
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é.
Code : Python Console1
2
3
4
>>> 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 fonction print. Donc, au lieu que ce soit la liste qui soit affichée, ce sont tous les nombres, séparés par des espaces. C'est exactement comme si vous aviez fait print(1, 4, 9, 16, 25, 36) .
Mais quel intérêt ? Ça 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 ça 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, ça signifie que les paramètres entrés 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 retournant une seconde, modifiée ou filtrée. Pour l'instant, nous allons voir une simple modification :
Code : Python Console1
2
3
4
>>> liste_origine = [0, 1, 2, 3, 4, 5]
>>> [nb * nb for nb in liste_origine]
[0, 1, 4, 9, 16, 25]
>>>
Je vous avais prévenus
.
Étudions un peu cette ligne. 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 :
Quand Python interprète cette ligne, il va parcourir la liste d'origine et mettre chaque élément de la liste au carré. Il retourne 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 :
Code : Python Console1
2
3
4
>>> 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 transfert que les valeurs paires. Au final, on se retrouve donc avec une liste deux fois plus petite que celle d'origine
.
N.B. : si vous travaillez sur un tuple, au lieu d'une liste, vous remplacerez les crochets encadrant les compréhensions de liste par des parenthèses.
Mélangeons un peu tout ça
Il est possible de filtrer et modifier une liste assez simplement. Par exemple, on a une liste contenant les quantités de marchandises stockées pour un magasin. Prenons des fruits, par exemple (je suis pas sectaire vous pouvez prendre des hamburgers si vous préférez
). Chaque semaine, le magasin va emprunter en stock une certaine quantité de chaque fruit, pour les 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 en profiter pour retirer celles qui sont inférieures ou égales à 0.
Code : Python Console1
2
3
4
5
>>> 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'achat. 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, les compréhensions de liste ne sont pas forcément simples dans leur syntaxe au début. Faites des essais, c'est aussi le meilleur moyen de comprendre.
Nouvelle application concrète
De nouveau, c'est à vous de bosser
.
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 avec la quantité restante en magasin. Nous verrons dans le chapitre suivant comment le faire avec des dictionnaires, pour l'instant on va se contenter de listes :
Code : Python Console1
2
3
4
5
6
7
8
>>> inventaire = [
... ("pomme", 22),
... ("melon", 4),
... ("poire", 18),
... ("fraise", 76),
... ("prune", 51),
... ]
>>>
Recopiez cette liste. Elle contient des tuples, contenant chacun un couple, le nom du fruit et sa quantité en magasin.
Votre mission : trier cette liste en fonction de la quantité de chaque fruit. Autrement dit, on doit obtenir quelque chose comme ça :
Code : Python1
2
3
4
5
6
7
[
("fraise", 76),
("prune", 51),
("pomme", 22),
("poire", 18),
("melon", 4),
]
Pour ceux qui n'ont pas eu la curiosité de regarder dans la documentation des listes, je signale à votre attention la méthode sort qui permet de trier une liste. Vous pouvez utiliser également la fonction sorted qui prend en paramètre la liste à trier (ce n'est pas une méthode de liste, faites attention). sorted retourne la liste triée, sans modifier l'originale, 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 ça, il faut l'inverser (autrement dit, placer la quantité avant le nom du fruit) pour pouvoir ensuite la trier par quantité.
Voici la correction que je vous propose :
Secret Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
Code : Python1
2
3
4
5
6
# on va placer l'inventaire dans l'autre sens, 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)]
Ca marche, et le traitement a été fait en deux lignes.
Si vous trouvez ça plus compréhensible, vous pouvez trier l'inventaire inversé avant la reconstitution. Il faut privilégier la lisibilité à la quantité de lignes.
Code : Python1
2
3
4
5
6
# on va placer l'inventaire dans l'autre sens, 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)]
Tu n'as pas dit qu'il fallait mettre des parenthèses à la place des crochets quand on travaillait sur des tuples ?
Si, absolument. Mais là on travaille sur une liste. Elle contient des tuples, c'est entendu, mais l'inventaire est une liste, et c'est l'inventaire que l'on parcourt grâce aux compréhensions de liste.
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 sont trop condensées pour être facilement comprises. Dans l'exemple précédent, on aurait très bien pu remplacer nos deux à trois lignes d'instructions par une seule, mais ça aurait été dur à lire. Ne sacrifiez pas la lisibilité au détriment de la longueur de votre code.
Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
Nous allons voir un moyen de transformer des chaînes en listes, et réciproquement.
Il est assez surprenant, de prime abord, d'avoir une conversion 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 d'une réelle conversion. Il va être difficile de démontrer l'utilité 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ée split ( « découper » en anglais). Cette méthode prend un paramètre, une autre chaîne, souvent d'un caractère, qui définit comment on va découper notre chaîne initiale.
C'est un peu compliqué et ça paraît très tordu... mais regardez plutôt :
Code : Python Console1
2
3
4
>>> ma_chaine = "Bonjour à tous"
>>> ma_chaine.split(" ")
['Bonjour', 'à', 'tous']
>>>
On passe en paramètre de la méthode split une chaîne contenant une unique espace. La méthode retourne 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éthode split, elle va découper la chaîne en fonction du paramètre donné, ici la première case de la liste va être du début jusqu'à la première espace (non incluse), puis de la première espace à la seconde, ainsi de suite jusqu'à la fin de la chaîne.
Sachez que split possède un paramètre par défaut, un code qui définit les espaces, les tabulations et les sauts de ligne. Donc vous pouvez très bien faire ma_chaine.split() , ça revient ici au même.
Des listes aux chaînes
Voyons l'inverse à présent : si on a une liste contenant des chaînes de caractères que nous souhaitons rassembler en une seule. On utilise la méthode de chaîne join (joindre en anglais
Code : Python Console1
2
3
4
>>> ma_liste = ['Bonjour', 'à', 'tous']
>>> " ".join(ma_liste)
'Bonjour à tous'
>>>
En paramètre de la méthode join, 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 une unique espace. Elle va insérer cette chaîne entre chaque chaîne 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 je ne trancherai pas pour ma part. 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 avons un nombre flottant dont nous souhaitons afficher la partie entière et la partie flottante, mais uniquement les trois premières décimales. Autrement dit, si on a un nombre flottant tel que 3.999999999999998, on souhaite avoir 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 :
Code : Python Console1
2
3
4
5
>>> afficher_flottant(3.99999999999998)
'3,999'
>>> afficher_flottant(1.5)
'1,5'
>>>
Voici la correction que je vous propose :
Secret Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
Code : Python 1
2
3
4
5
6
7
8
9
10
11
12
13
14
def afficher_flottant(flottant):
"""Fonction prenant en paramètre un flottant et retournant une chaîne
de caractères avec ce nombre tronqué. La partie flottante
doit être d'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 assure qu'il n'y aura pas d'erreurs 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é par vous-même, regardez bien cette solution, elle n'est pas forcément simple au premier coup d'oeil. 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 avec une liste inconnue de paramètres.
Notez malgré tout que ce point est assez délicat. Si vous n'arrivez pas bien à le comprendre, laissez cette sous-partie de côté, ça ne vous pénalisera pas
Les fonctions dont on ne connaît pas le nombre de paramètres à l'avance
Vous devriez tout de suite penser à la fonction print : on lui passe une liste de paramètres que la fonction va afficher, dans l'ordre où ils sont placés, séparés par une espace (ou tout autre délimiteur choisi).
Vous n'allez peut-être pas trouver sur le moment d'applications de cette fonctionnalité, mais tôt ou tard cela arrivera. La syntaxe est tellement simple que c'en est déconcertant :
Code : Python1
def fonction(*parametres):
On place une étoile * devant le nom du paramètre qui accueillera la liste des paramètres. Voyons un peu plus précisément comment cela se présente :
Code : Python Console 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> def fonction_inconnue(*parametres):
... """Test d'une fonction pouvant être appelée avec un nombre variable
... de paramètres.
... """
... print("J'ai reçu : {0}.".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 notre fonction_inconnue avec un nombre indéterminé de paramètres, de 0 à l'infini (enfin, théoriquement
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 entrez fonction_inconnue(couleur="rouge") , vous allez avoir une erreur : fonction_inconnue() got an unexpected keyword argument 'couleur' . Nous verrons dans le chapitre suivant comment capturer ces paramètres nommés.
Vous pouvez bien entendu définir une fonction avec plusieurs paramètres que l'on doit rentrer quoi qu'il arrive, et ensuite une liste de paramètres variables :
Code : Python1
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
Si on définit une liste variable de paramètres, elle doit se trouver après la liste des paramètres standards.
Cela est évident au fond. Vous ne pouvez avoir une définition de fonction comme def 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 fonction print.
Citation
print(value, ..., sep=' ', end='\n', file=sys.stdout)
Ne nous occupons pas du dernier paramètre. Il définit le descripteur vers lequel print envoie 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 :
Code : Python1
def print(*values, sep=' ', end='\n', file=sys.stdout):
Petit exercice : faire une fonction afficher identique à print, c'est-à-dire prenant un nombre indéterminé de paramètres, les affichant en les séparant à l'aide du paramètre nommé sep et terminant l'affichage par la variable fin. Notre fonction afficher ne comptera pas de paramètre file. En outre, elle devra passer par print pour afficher (on ne connaît pas encore d'autres façons de faire). La seule contrainte est que l'appel à print ne 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 la docstring que j'ai écrite :
Code : Python1
2
3
4
5
6
7
8
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='')
"""
Voici la solution que je vous propose :
Secret Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
Code : Python 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 = 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. Au contraire, tant que vous comprenez la solution que je propose
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é.
Code : Python Console1
2
3
4
>>> 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 fonction print. Donc, au lieu que ce soit la liste qui soit affichée, ce sont tous les nombres, séparés par des espaces. C'est exactement comme si vous aviez fait print(1, 4, 9, 16, 25, 36) .
Mais quel intérêt ? Ça 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 ça 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, ça signifie que les paramètres entrés 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 retournant une seconde, modifiée ou filtrée. Pour l'instant, nous allons voir une simple modification :
Code : Python Console1
2
3
4
>>> liste_origine = [0, 1, 2, 3, 4, 5]
>>> [nb * nb for nb in liste_origine]
[0, 1, 4, 9, 16, 25]
>>>
Je vous avais prévenus
Étudions un peu cette ligne. 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 variable nb, on sait juste qu'il faut la mettre au carré. Notez qu'on aurait pu écrire nb**2 , cela revient au même.
- for nb in liste_origine : voilà d'où vient notre variable nb. On reconnaît la syntaxe d'une boucle for, 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 retourne 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 :
Code : Python Console1
2
3
4
>>> 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 transfert que les valeurs paires. Au final, on se retrouve donc avec une liste deux fois plus petite que celle d'origine
N.B. : si vous travaillez sur un tuple, au lieu d'une liste, vous remplacerez les crochets encadrant les compréhensions de liste par des parenthèses.
Mélangeons un peu tout ça
Il est possible de filtrer et modifier une liste assez simplement. Par exemple, on a une liste contenant les quantités de marchandises stockées pour un magasin. Prenons des fruits, par exemple (je suis pas sectaire vous pouvez prendre des hamburgers si vous préférez
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 en profiter pour retirer celles qui sont inférieures ou égales à 0.
Code : Python Console1
2
3
4
5
>>> 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'achat. 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, les compréhensions de liste ne sont pas forcément simples dans leur syntaxe au début. Faites des essais, c'est aussi le meilleur moyen de comprendre.
Nouvelle application concrète
De nouveau, c'est à vous de bosser
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 avec la quantité restante en magasin. Nous verrons dans le chapitre suivant comment le faire avec des dictionnaires, pour l'instant on va se contenter de listes :
Code : Python Console1
2
3
4
5
6
7
8
>>> inventaire = [
... ("pomme", 22),
... ("melon", 4),
... ("poire", 18),
... ("fraise", 76),
... ("prune", 51),
... ]
>>>
Recopiez cette liste. Elle contient des tuples, contenant chacun un couple, le nom du fruit et sa quantité en magasin.
Votre mission : trier cette liste en fonction de la quantité de chaque fruit. Autrement dit, on doit obtenir quelque chose comme ça :
Code : Python1
2
3
4
5
6
7
[
("fraise", 76),
("prune", 51),
("pomme", 22),
("poire", 18),
("melon", 4),
]
Pour ceux qui n'ont pas eu la curiosité de regarder dans la documentation des listes, je signale à votre attention la méthode sort qui permet de trier une liste. Vous pouvez utiliser également la fonction sorted qui prend en paramètre la liste à trier (ce n'est pas une méthode de liste, faites attention). sorted retourne la liste triée, sans modifier l'originale, 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 ça, il faut l'inverser (autrement dit, placer la quantité avant le nom du fruit) pour pouvoir ensuite la trier par quantité.
Voici la correction que je vous propose :
Secret Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!
Code : Python1
2
3
4
5
6
# on va placer l'inventaire dans l'autre sens, 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)]
Ca marche, et le traitement a été fait en deux lignes.
Si vous trouvez ça plus compréhensible, vous pouvez trier l'inventaire inversé avant la reconstitution. Il faut privilégier la lisibilité à la quantité de lignes.
Code : Python1
2
3
4
5
6
# on va placer l'inventaire dans l'autre sens, 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)]
Tu n'as pas dit qu'il fallait mettre des parenthèses à la place des crochets quand on travaillait sur des tuples ?
Si, absolument. Mais là on travaille sur une liste. Elle contient des tuples, c'est entendu, mais l'inventaire est une liste, et c'est l'inventaire que l'on parcourt grâce aux compréhensions de liste.
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 sont trop condensées pour être facilement comprises. Dans l'exemple précédent, on aurait très bien pu remplacer nos deux à trois lignes d'instructions par une seule, mais ça aurait été dur à lire. Ne sacrifiez pas la lisibilité au détriment de la longueur de votre code.
Ce lien n'est pas visible, veuillez vous connecter pour l'afficher. Je m'inscris!