Troisième article à propos des notions avancées de Python. Si vous avez raté les autres, vous pouvez les retrouver ici : Notions Avancées - Collection. Cette fois ci, nous allons baisser un peu en difficulté. En effet, le dernier article à propos des décorateurs était un peu compliqué j'avoue, et vous allez pouvoir souffler un peu. Mais pas trop non plus, les fonctions anonymes sont aussi un petit peu étranges. En revanche la deuxième partie à propos des fichiers devrait être plus simple. Allez c'est parti !
Les fonctions lambda sont des fonctions dites "anonymes" (d'où l'appellation "lambda"), c'est à dire qu'elles n'ont pas de nom. Si vous avez déjà fait du Javascript, du Java, du C# ou même du C++, vous connaissez sûrement déjà ce type de fonction.
En Python, ces fonctions permettent surtout d'avoir un code plus lisible, elles n'apportent pas vraiment de fonctionnalité supplémentaire. Mais ce n'est pas pour ça qu'il ne faut pas les utiliser ! En effet, dans certains cas leur utilisation est préférable, même si on pourrait effectuer la même chose avec des fonctions "classiques". Par exemple, pour effectuer des actions simples, il est mieux d'utiliser des fonctions lambda car vous pouvez les écrire en une ligne.
Voici un exemple de fonction lambda :
>>> hello = lambda : print("Hello")
>>> hello()
Hello
Voilà ! Nous avons créé une simple fonction qui affiche "Hello", et cela est fait en une ligne. Voici l'équivalent avec la syntaxe habituelle :
>>>def hello():
print("Hello")
>>> hello()
Hello
Cela prend 3 lignes, alors qu'au dessus nous avions besoin seulement de 2 lignes pour effectuer exactement la même chose. Et encore, on aurait pu faire plus court. Regardez :
>>> (lambda : print("Hello"))()
Hello
On obtient encore une fois la même chose, en une ligne cette fois !
Et du coup ? T'expliques la syntaxe maintenant ?
J'y viens. En fait pour initialiser une fonction anonyme comme vous avez pu le voir on utilise le mot clé lambda
. Ensuite :
, puis l'instruction qu'on souhaite exécuter. Cela renvoie une fonction lambda, vous pouvez donc stocker le résultat dans une variable qui sera le nom de la fonction.
Eventuellement, vous pouvez ne pas stocker le résultat dans une variable et simplement instancier la fonction renvoyée si jamais vous n'en avez besoin qu'une seule fois. C'est ce que j'ai fait dans le deuxième exemple. Pour cela il faut mettre votre définition de fonction lambda entre parenthèses, et ajouter des parenthèses après la définition de la fonction pour indiquer qu'on souhaite l'instancier.
Evidemment ces fonctions seraient rapidement inutiles si on ne pouvait pas modifier leur comportement via des paramètres. Voici donc un exemple de fonction lambda à paramètres :
>>> dire_bonjour = lambda prenom: print("Bonjour", prenom)
>>> dire_bonjour("Jean")
Bonjour Jean
Pour ajouter un paramètre à une fonction lambda, on précise simplement le nom de ce paramètre après le mot clé lambda et avant les deux points. Puis on peut ensuite récupérer ce paramètre afin de l'utiliser dans la définition de notre fonction. Finalement, pour exécuter notre fonction avec notre paramètre, c'est exactement comme une fonction classique.
Si on souhaite maintenant utiliser plusieurs paramètres, c'est la même syntaxe, sauf qu'on sépare ces paramètres par une virgule.
>>> moyenne = lambda x, y, z: (x+y+z)/3
>>> moyenne(10, 20, 15)
15.0
Comme vous avez pu le constater, nous n'avons écrit nos fonctions lambda que sur une ligne jusqu'ici. Vous pouvez si vous le souhaitez les écrire sur plusieurs lignes. Par contre, elles resteront toujours des expressions uniques et s'exécuteront comme une seule ligne de code.
Mais comment exécuter plusieurs instructions du coup ?
L'astuce est de rassembler nos actions dans un tuple. De cette façon nos actions s'exécutent les unes après les autres. Par contre cela nous renvoie donc un tuple avec les résultats de chacune des actions exécutées, donc ce n'est pas très pratique pour récupérer des résultats. Cette syntaxe n'est pas du tout recommandée, mais sachez que ça existe et que c'est donc possible.
>>> multi_actions = lambda x, y: (print("Action 1"), print("Action 2"), (x+y)/2)
>>> multi_actions(5, 10)
Action 1
Action 2
(None, None, 7.5)
Souvent, les fonctions lambda sont utilisées avec les fonctions map
et filter
. Si vous ne les connaissez pas, ce sont des fonctions utiles lorsqu'on souhaite travailler avec des itérateurs. La fonction map
nous permet d'itérer sur un itérateur est d'exécuter une fonction pour chaque élément de cet itérateur. La fonction filter
permet d'appliquer un filtre sur un itérateur afin de générer un sous-itérateur.
Ces fonctions prennent en paramètres une fonction, et un itérateur. C'est pourquoi il peut être utile d'utiliser des fonctions lambda dans ce cas, car ce sont des fonctions qui ne sont utilisées qu'une fois, pour effectuer une opération particulière et c'est tout.
>>> scores = [234, 526, 169, 632]
>>> list(map((lambda x: x*x), scores))
[54756, 276676, 28561, 399424] # On met chaque score au carré
>>> list(filter(lambda x: x >= 500, scores))
[526, 632] # On ne garde que les scores supérieurs à 500
Il est possible de très mal utiliser ces fonctions. Mais il est également possible de bien les utiliser. C'est pourquoi nous allons voir un petit récap des cas d'usages de ces fonctions.
Tout d'abord, il ne faut jamais les utiliser lorsque vous devez les assigner à une variable. Le premier exemple que je vous ai montré était donc très mauvais, mais c'était juste pour comprendre le concept. En effet, si vous leur donnez un nom, cela signifie que vous comptez utiliser cette fonction plusieurs fois, il est donc préférable d'utiliser la syntaxe d'une fonction classique.
Ensuite, il ne faut jamais les utiliser lorsque vous souhaitez effectuer plusieurs actions, comme je vous ai montré avec les tuples. En effet, ma syntaxe s'apparente plutôt à une sorte de "hack", du bidouillage quoi, et on aime pas ça ! Les fonctions lambda ne sont faites que pour exécuter une action et non plusieurs.
Finalement, le seul cas d'usage restant est lorsque vous avez besoin d'effectuer une action unique, pas trop compliquée et donc codable en une seule ligne. Comme pour les fonctions map
et filter
par exemple, où l'utilisation de fonctions anonymes s'avère plutôt judicieuse, améliorant la lisibilité du code (attention cependant car souvent les fonctions map
et filter
alourdissent le code, puisqu'on peut effectuer la même chose avec les compréhensions de listes).
La maîtrise des fichiers est essentielle en Python. A la fois pour faire persister des données dans vos programmes, mais aussi pour pouvoir utiliser des ressources extérieures.
Avant d'exécuter des actions avec un fichier, il est nécessaire de l'ouvrir. Pour cela, on peut utiliser la fonction open
. Cette fonction prend en paramètres le chemin du fichier à ouvrir (absolu ou relatif) et le mode d'ouverture.
Voici les différents modes d'ouverture possibles :
La fonction open
nous renvoie un fichier sous forme d'objet.
Pour commencer, nous allons donc créer un fichier, et écrire dedans.
file = open("test.txt", "w")
file.write("Hello ProPython !")
file.close()
Pour créer un fichier on aurait pu utiliser le mode "x" ?
Oui, mais personnellement je préfère utiliser le mode "w", car celui-ci ne génère pas d'erreur si le fichier existe déjà. En fait le mode "x" n'est vraiment utile que lorsque vous voulez être sûr de créer un fichier qui n'existe pas déjà.
Ensuite, pour écrire dans le fichier qu'on vient de créer, on utilise la méthode write
avec ce que l'on souhaite écrire sous forme de chaîne de caractères (ou sous forme de bytes si vous utilisez les modes binaires). Attention, le paramètre de la méthode write
doit toujours être une chaîne de caractères, par exemple vous ne pouvez pas écrire une liste, ou alors vous devez d'abord la convertir en str
.
Si vous recopiez le code au dessus dans votre interpréteur Python, vous n'aurez peut-être pas les accès pour créer un fichier là où votre interpréteur est situé. Je vous recommande donc d'utiliser un IDE pour manipuler des fichiers (ou bien d'utiliser des chemins absolus, ex : file = open("C:\\Users\\ProPython\\Documents\\test.txt", "w")
.
Maintenant, si vous ouvrez votre fichier nouvellement créé, vous devriez retrouver à l'intérieur la chaîne de caractères passée à la méthode write
.
Avant d'essayer de lire dans un fichier, nous allons en créer un pour nos tests. Pour cela, on reprend le code ci-dessus en rajoutant un peu de contenu.
file = open("test.txt", "w")
file.write("Hello ProPython !\nCeci est un fichier test.\nBon courage...")
file.close()
Notre fichier est maintenant créé. On peut l'ouvrir en mode lecture avec la fonction open
.
file = open("test.txt", "r")
Pour lire le contenu d'un fichier, on utilise la méthode read
. Elle nous renvoie une chaîne de caractères correspondant au contenu du fichier. On peut également lui passer en paramètre le nombre de caractères qu'on souhaite lire.
print(file.read())
# Hello ProPython !
# Ceci est un fichier test.
# Bon courage...
print(file.read(8))
# Hello Pr
On peut également lire ligne par ligne avec la méthode readline
.
print(file.readline())
print(file.readline())
print(file.readline())
# Hello ProPython !
# Ceci est un fichier test.
# Bon courage...
Si on souhaite lire tout le contenu ligne par ligne, on peut simplement itérer sur notre objet fichier.
for line in f:
print(line)
On obtient le même résultat qu'au dessus.
Une fois qu'on a fini de travailler sur notre fichier, une bonne pratique est de le fermer.
file.close()
Jusque là, nous avons uniquement vu la façon classique d'ouvrir un fichier. Il existe une autre façon : en utilisant le mot-clé with
. Ce qui donne ceci :
with open("test.txt", "r") as file:
file.write("Hello ProPython !")
Ce code est équivalent à celui vu précédemment. Cependant il présente quelques avantages. En effet, avec cette syntaxe nous n'avons pas à gérer certaines exceptions qui pourraient entraver la fermeture du fichier, puisqu'il est fermé automatiquement et dans tous les cas à la sortie du bloc d'instructions with
. Imaginons par exemple que vous ouvrez un fichier sans with
. Vous effectuez quelques opérations sur votre fichier et l'une d'elles génère une erreur. Dans ce cas, si vous n'avez pas géré l'erreur, votre programme plante et le fichier n'est pas fermé. Avec with
, même si une erreur est levée, votre fichier sera quand même fermé.
C'est un module utilisé pour la sérialisation/désérialisation de données, c'est à dire qu'il permet de transformer les données en un format transmissible et reconstructible. Dans notre cas, il va nous permettre de stocker des structures de données et des objets plus complexes dans des fichiers. C'est un module intégré dans la bibliothèque standard, donc vous n'avez pas besoin de l'installer et vous pouvez l'importer simplement de cette façon :
import pickle
Pickle travaille avec des données binaires, donc les modes à utiliser pour lire et écrire dans des fichiers seront les modes binaires.
Pour enregistrer des données, il faut d'abord commencer par ouvrir notre fichier en écriture binaire.
with open("test.txt", "wb") as file:
On utilise la syntaxe with
qui est quasiment tout le temps préférable à l'autre syntaxe. Ensuite, pour enregistrer un objet dans notre fichier, on utilise la fonction dump
du module pickle
. Cette fonction prend en paramètres l'objet à enregistrer, et le fichier dans lequel enregistrer l'objet.
test_list = [1, 2, 3]
pickle.dump(test_list, file)
Bien sûr, n'oubliez pas d'importer le module pickle
.
Et puis voilà, c'est tout ! Vous voyez, c'est très simple d'enregistrer des objets dans des fichiers, et pour la lecture il en est de même, c'est ce que nous allons voir maintenant.
Pour lire des données, on utilise cette fois la fonction load
du module pickle
. Elle ne prend qu'un paramètre : le fichier à partir duquel on souhaite charger nos données. Aussi, on ouvre notre fichier en lecture binaire, plutôt qu'en écriture binaire. Ainsi, en reprenant l'exemple précédent, on peut récupérer notre liste de cette façon :
with open("test.txt", "rb") as file:
my_list = pickle.load(file)
print(my_list)
# [1, 2, 3]
La façon la plus simple de sauvegarder plusieurs objets avec Pickle, c'est de les stocker dans un conteneur comme une liste, un dictionnaire, etc... Notez qu'il est tout à fait possible d'utiliser dump
plusieurs fois dans un même fichier, et ensuite d'utiliser load
plusieurs fois pour récupérer plusieurs objets, mais cela est plus difficile et rend notre code moins lisible que de simplement stocker nos objets dans un conteneur. Par exemple, si vous avez créé une classe Joueur
et que vous souhaitez stocker deux objets Joueur
dans un fichier, il est mieux de les mettre dans une liste et d'enregistrer cette liste plutôt que d'enregistrer ces deux joueurs en deux fois.
# Bien
liste_joueurs = [joueur1, joueur2]
pickle.dump(liste_joueurs, file)
liste_joueurs = pickle.load(file)
# Pas bien
pickle.dump(joueur1, file)
pickle.dump(joueur2, file)
joueur1 = pickle.load(file)
joueur2 = pickle.load(file)
Voilà, vous savez maintenant manipuler des fonctions anonymes et enregistrer puis récupérer des données dans des fichiers.
Autant les fonctions lambda sont plus une question de style et de lisibilité plutôt qu'une réelle fonctionnalité, par contre la manipulation de fichiers est essentielle, par exemple si vous créez un jeu, il peut être bien de stocker les scores en local plutôt que sur un serveur, donc il faut manipuler des fichiers.
J'espère que cet article vous aura plu, si vous avez des questions n'hésitez pas à me contacter, soit via le site, soit par mail, je me ferai un plaisir de vous répondre. Vous pouvez également laisser un commentaire.
On se retrouve prochainement pour la suite de votre apprentissage du Python !