Modules, fichiers, expressions régulières

Le langage Python est défini par un ensemble de règle, une grammaire. Seul, il n’est bon qu’à faire des calculs. Les modules sont des collections de fonctionnalités pour interagir avec des capteurs ou des écrans ou pour faire des calculs plus rapides ou plus complexes.

Fichiers

Les fichiers permettent deux usages principaux :

  • récupérer des données d’une exécution du programme à l’autre (lorsque le programme s’arrête, toutes les variables sont perdues)

  • échanger des informations avec d’autres programmes (Excel par exemple).

Le format le plus souvent utilisé est le fichier plat, texte, txt, csv, tsv. C’est un fichier qui contient une information structurée sous forme de matrice, en ligne et colonne car c’est comme que les informations numériques se présentent le plus souvent. Un fichier est une longue séquence de caractères. Il a fallu choisir une convention pour dire que deux ensembles de caractères ne font pas partie de la même colonne ou de la même ligne. La convention la plus répandue est :

  • \t : séparateur de colonnes

  • \n : séparateur de lignes

Le caractère \ indique au langage python que le caractère qui suit fait partie d’un code. Vous trouverez la liste des codes : String and Bytes literals.

Aparté : aujourd’hui, lire et écrire des fichiers est tellement fréquent qu’il existe des outils qui font ça dans une grande variété de formats. Il est utile pourtant de le faire au moins une fois soi-même pour comprendre la logique des outils et ne pas être bloqué dans les cas non prévus.

Ecrire et lire des fichiers est beaucoup plus long que de jouer avec des variables. Ecrire signifie qu’on enregistre les données sur le disque dur : elles passent du programme au disque dur (elles deviennent permanentes). Elles font le chemin inverse lors de la lecture.

Ecriture

Il est important de retenir qu’un fichier texte ne peut recevoir que des chaînes de caractères.

[16]:
mat = [[1.0, 0.0], [0.0, 1.0]]  # matrice de type liste de listes
with open("mat.txt", "w") as f:  # création d'un fichier en mode écriture
    for i in range(0, len(mat)):  #
        for j in range(0, len(mat[i])):  #
            s = str(mat[i][j])  # conversion en chaîne de caractères
            f.write(s + "\t")  #
        f.write("\n")  #

# on vérifie que le fichier existe :
import os

print([_ for _ in os.listdir(".") if "mat" in _])

# la ligne précédente utilise le symbole _ : c'est une variable
# le caractère _ est une lettre comme une autre
# on pourrait écrire :
# print([ fichier for fichier in os.listdir(".") if "mat" in fichier ] )
# on utilise cette convention pour dire que cette variable n'a pas vocation à rester
['mat2.txt', 'mat.txt']

Le même programme mais écrit avec une écriture condensée :

[17]:
mat = [[1.0, 0.0], [0.0, 1.0]]  # matrice de type liste de listes
with open("mat.txt", "w") as f:  # création d'un fichier
    s = "\n".join("\t".join(str(x) for x in row) for row in mat)
    f.write(s)

# on vérifie que le fichier existe :
print([_ for _ in os.listdir(".") if "mat" in _])
['mat2.txt', 'mat.txt']

On regarde les premières lignes du fichier mat.txt :

[18]:
with open("mat.txt", "r") as f:
    print(f.read()[:1000])
1.0     0.0
0.0     1.0

Lecture

[19]:
with open("mat.txt", "r") as f:  # ouverture d'un fichier
    mat = [row.strip(" \n").split("\t") for row in f.readlines()]
print(mat)
[['1.0', '0.0'], ['0.0', '1.0']]

On retrouve les mêmes informations à ceci près qu’il ne faut pas oublier de convertir les nombres initiaux en float.

[20]:
with open("mat.txt", "r") as f:  # ouverture d'un fichier
    mat = [[float(x) for x in row.strip(" \n").split("\t")] for row in f.readlines()]
print(mat)
[[1.0, 0.0], [0.0, 1.0]]

Voilà qui est mieux. Le module os.path propose différentes fonctions pour manipuler les noms de fichiers. Le module os propose différentes fonctions pour manipuler les fichiers :

[21]:
import os

for f in os.listdir("."):
    print(f)
monmodule.py
pyramide_bigarree.ipynb
td2_1.png
classes_heritage_correction.ipynb
lemonde5.txt
VOEUX83.txt
classes_carre_magique.ipynb
lemonde3.txt
blogny1.txt
blog3.txt
nytimes5.txt
classes_carre_magique_correction.ipynb
integrale_rectangle_correction.ipynb
nytimes2.txt
VOEUX05.txt
module_file_regex.ipynb
__pycache__
int2.png
lemonde1.txt
nytimes8.txt
VOEUX06.txt
dictionnaire_vigenere.ipynb
articles.zip
voeux.zip
lemonde9.txt
VOEUX08.txt
2048.png
texte_langue_correction.ipynb
nytimes1.txt
VOEUX01.txt
premiers_pas_correction.ipynb
nytimes9.txt
VOEUX74.txt
j2048.ipynb
VOEUX75.txt
elpais2.txt
lequipe1.txt
VOEUX94.txt
lemonde7.txt
lemonde10.txt
seance4_excel.csv
arthur_charpentier2.txt
afp2.txt
inconnu2.txt
pyramide_bigarree_correction.ipynb
afp1.txt
lemonde8.txt
mat2.txt
elpais5.txt
hexa.png
int.png
module_file_regex_correction.ipynb
nytimes3.txt
VOEUX89.txt
arthur_charpentier3.txt
VOEUX87.txt
mat.txt
lemonde11.txt
elpais4.txt
nytimes7.txt
lemonde2.txt
texte.txt
inconnu1.txt
lequipe2.txt
premiers_pas.ipynb
lemonde6.txt
blog1.txt
VOEUX09.txt
VOEUX90.txt
elpais7.txt
exemple_fichier.txt
elpais6.txt
texte_langue.ipynb
integrale_rectangle.ipynb
VOEUX07.txt
classes_heritage.ipynb
arthur_charpentier1.txt
lemonde4.txt
blog2.txt
dictionnaire_vigenere_correction.ipynb
marge.png
variable_boucle_tests.ipynb
nytimes6.txt
elpais3.txt
variable_boucle_tests_correction.ipynb
j2048_correction.ipynb
nytimes4.txt
VOEUX79.txt
elpais1.txt

with

De façon pragmatique, l’instruction with permet d’écrire un code plus court d’une instruction : close. Les deux bouts de code suivant sont équivalents :

[22]:
with open("exemple_fichier.txt", "w") as f:
    f.write("something")
[23]:
f = open("exemple_fichier.txt", "w")
f.write("something")
f.close()

L’instruction close ferme le fichier. A l’ouverture, le fichier est réservé par le programme Python, aucune autre application ne peut écrire dans le même fichier. Après l’instruction close, une autre application pour le supprimer, le modifier. Avec le mot clé with, la méthode close est implicitement appelée.

à quoi ça sert ?

On écrit très rarement un fichier texte. Ce format est le seul reconnu par toutes les applications. Tous les logiciels, tous les langages proposent des fonctionnalités qui exportent les données dans un format texte. Dans certaines circonstances, les outils standards ne fonctionnent pas - trop grops volumes de données, problème d’encoding, caractère inattendu -. Il faut se débrouiller.

[ ]:

Exercice 1 : Excel \rightarrow Python \rightarrow Excel

Il faut télécharger le fichier seance4_excel.csv qui contient une table de trois colonnes. Il faut :

  • le lire sous python

  • créer une matrice carrée 3x3 où chaque valeur est dans sa case (X,Y),

  • enregistrer le résultat sous format texte,

  • le récupérer sous Excel.

Autres formats de fichiers

Les fichiers texte sont les plus simples à manipuler mais il existe d’autres formats classiques :

  • json : un format d’échange de données très utilisés sur internet

  • html : les pages web

  • xml : données structurées

  • zip, gz : données compressées

  • wav, mp3, ogg : musique

  • mp4, Vorbis : vidéo

Modules

Les modules sont des extensions du langages. Python ne sait quasiment rien faire seul mais il bénéficie de nombreuses extensions. On distingue souvent les extensions présentes lors de l’installation du langage (le module math) des extensions externes qu’il faut soi-même installer (numpy). Deux liens :

Le premier réflexe est toujours de regarder si un module ne pourrait pas vous être utile avant de commencer à programmer. Pour utiliser une fonction d’un module, on utilise l’une des syntaxes suivantes :

[24]:
import math

print(math.cos(1))

from math import cos

print(cos(1))

from math import *  # cette syntaxe est déconseillée car il est possible qu'une fonction

print(cos(1))  # porte le même nom qu'une des vôtres
0.5403023058681398
0.5403023058681398
0.5403023058681398

Exercice 2 : trouver un module (1)

Aller à la page modules officiels (ou utiliser un moteur de recherche) pour trouver un module permettant de générer des nombres aléatoires. Créer une liste de nombres aléatoires selon une loi uniforme puis faire une permutation aléatoire de cette séquence.

Exercice 3 : trouver un module (2)

Trouver un module qui vous permette de calculer la différence entre deux dates puis déterminer le jour de la semaine où vous êtes nés.

Module qu’on crée soi-même

Il est possible de répartir son programme en plusieurs fichiers. Par exemple, un premier fichier monmodule.py qui contient une fonction :

[25]:
# fichier monmodule.py
import math


def fonction_cos_sequence(seq):
    return [math.cos(x) for x in seq]


if __name__ == "__main__":
    print("ce message n'apparaît que si ce programme est le point d'entrée")
ce message n'apparaît que si ce programme est le point d'entrée

La cellule suivante vous permet d’enregistrer le contenu de la cellule précédente dans un fichier appelée monmodule.py.

[26]:
code = """
# coding: utf-8
import math


def fonction_cos_sequence(seq) :
    return [math.cos(x) for x in seq]


if __name__ == "__main__" :
    print("ce message n'apparaît que si ce programme est le point d'entrée")
"""
with open("monmodule.py", "w", encoding="utf-8") as f:
    f.write(code)

Python recherche les modules qu’il peut importer parmi les répertoires de la liste :

[2]:
import sys

sys.path[-3:]
[2]:
['/usr/lib/python3.10/lib-dynload',
 '',
 '/home/xadupre/.venv/lib/python3.10/site-packages']

On ajoute le répertoire courant pour que python puisse trouver le nouveau module.

[3]:
sys.path.append(".")

Le second fichier :

[4]:
import monmodule

print(monmodule.fonction_cos_sequence([1, 2, 3]))
[0.5403023058681398, -0.4161468365471424, -0.9899924966004454]

Note : Si le fichier monmodule.py est modifié, python ne recharge pas automatiquement le module si celui-ci a déjà été chargé. On peut voir la liste des modules en mémoire dans la variable sys.modules :

[28]:
import sys

list(sorted(sys.modules))[:10]
[28]:
['IPython',
 'IPython.core',
 'IPython.core.alias',
 'IPython.core.application',
 'IPython.core.async_helpers',
 'IPython.core.autocall',
 'IPython.core.builtin_trap',
 'IPython.core.compilerop',
 'IPython.core.completer',
 'IPython.core.completerlib']

Pour retirer le module de la mémoire, il faut l’enlever de sys.modules avec l’instruction del sys.modules['monmodule']. Python aura l’impression que le module monmodule.py est nouveau et il l’importera à nouveau.

Exercice 4 : son propre module

Que se passe-t-il si vous remplacez if __name__ == "__main__": par if True :, ce qui équivaut à retirer la ligne if __name__ == "__main__": ?

Expressions régulières

Pour la suite de la séance, on utilise comme préambule les instructions suivantes :

[29]:
from teachpyx.tools.data_helper import download_and_unzip

url = "https://github.com/sdpython/teachpyx/raw/main/_data/voeux.zip"
discours = download_and_unzip(url)
discours[:5]
[29]:
['VOEUX01.txt', 'VOEUX05.txt', 'VOEUX06.txt', 'VOEUX07.txt', 'VOEUX08.txt']

La documentation pour les expressions régulières est ici : regular expressions. Elles permettent de rechercher des motifs dans un texte :

  • 4 chiffres / 2 chiffres / 2 chiffres correspond au motif des dates, avec une expression régulière, il s’écrit : [0-9]{4}/[0-9]{2}/[0-9]{2}

  • la lettre a répété entre 2 et 10 fois est un autre motif, il s’écrit : a{2,10}.

[30]:
import re  # les expressions régulières sont accessibles via le module re

expression = re.compile("[0-9]{2}/[0-9]{2}/[0-9]{4}")
texte = """Je suis né le 28/12/1903 et je suis mort le 08/02/1957. Ma seconde femme est morte le 10/11/63."""
cherche = expression.findall(texte)
print(cherche)
['28/12/1903', '08/02/1957']

Pourquoi la troisième date n’apparaît pas dans la liste de résultats ?

Exercice 5 : chercher un motif dans un texte

On souhaite obtenir toutes les séquences de lettres commençant par je ? Quel est le motif correspondant ? Il ne reste plus qu’à terminer le programme précédent.

Exercice 6 : chercher un autre motif dans un texte

Avec la même expression régulière, rechercher indifféremment le mot securite ou insecurite.

Exercice 7 : recherche les urls dans une page wikipédia

On pourra prendre comme exemple la page du programme Python.

Exercice 8 : construire un texte à motif

A l’inverse des expressions régulières, des modules comme Mako ou Jinja2 permettent de construire simplement des documents qui suivent des règles. Ces outils sont très utilisés pour la construction de page web. On appelle cela faire du templating. Créer une page web qui affiche à l’aide d’un des modules la liste des dimanches de cette année.

Petites subtilités avec les expressions régulières en Python

Je me souviens rarement de la syntaxe des expressions régulières. J’utilise beaucoup la fonction findall. A tort je crois.

[2]:
import re

text = """ a ab ab ab ab c a ab ab ab"""
exp = re.compile("a( ((ab)|(c)))+")
found = exp.findall(text)
for el in found:
    print(el)
(' c', 'c', 'ab', 'c')
(' ab', 'ab', 'ab', '')

Le premier élément de chaque ligne correspond au groupe inclus dans les premières parenthèses qui matche plusieurs sous-parties de la chaîne de caractères mais seule la dernière est conservée.

[3]:
text = """ a ab ab ab ab c a ab ab ab"""
exp = re.compile("a(( ((ab)|(c)))+)")
found = exp.findall(text)
for el in found:
    print(el)
(' ab ab ab ab c', ' c', 'c', 'ab', 'c')
(' ab ab ab', ' ab', 'ab', 'ab', '')

Si on ajoute des parenthèses autour de l’expression répétées (donc incluant le signe +), on récupère toutes les sous-parties matchant le motif répété par +. Naïvement, j’ai pensé que je les aurais toutes dans des éléments séparés. Mais si l’expression régulières contient n groupes de parenthèses, on récupère des tuples de n éléments. Un autre code permet de récupèrer les positions.

[4]:
exp = re.compile("a(( ((ab)|(c)))+)")
for m in exp.finditer(text):
    print("%02d-%02d: %s" % (m.start(), m.end(), m.groups()))
01-16: (' ab ab ab ab c', ' c', 'c', 'ab', 'c')
17-27: (' ab ab ab', ' ab', 'ab', 'ab', None)

On se rend compte plus rapidement que quelque chose ne va pas.


Notebook on github