Maîtriser les expressions régulières

Download nbviewer Onyxia
Binder Open In Colab githubdev

</p>

Ce TD est directement issu du contenu de Xavier Dupré, l’ancien professeur de ce cours. Il sera mis-à-jour dans le futur.

Chercher un mot dans un texte est une tâche facile, c’est l’objectif de la méthode find() attachée aux chaînes de caractères, étudiée dans la partie webscraping. Elle suffit également lorsque l’on cherche un mot au pluriel ou au singulier, mais il faut dans ce cas l’appeler au moins deux fois.

Pour des expressions plus compliquées, il est conseillé d’utiliser les expressions régulières, ou “regex”. C’est une fonctionnalité qu’on retrouve dans beaucoup de langages. C’est une forme de grammaire qui permet de rechercher des expressions.

Par exemple, lorsqu’on remplit un formulaire, on voit souvent le format "MM/JJ/AAAA" qui précise sous quelle forme on s’attend à ce qu’une date soit écrite. Les expressions régulières permettent de définir également ce format et de chercher dans un texte toutes les chaînes de caractères qui sont conformes à ce format.

La liste qui suit contient des dates de naissance. On cherche à obtenir toutes les dates de cet exemple sachant que les jours ou les mois contiennent un ou deux chiffres, et les années deux ou quatre.

In [2]:
s = """date 0 : 14/9/2000
date 1 : 20/04/1971     date 2 : 14/09/1913     date 3 : 2/3/1978
date 4 : 1/7/1986     date 5 : 7/3/47     date 6 : 15/10/1914
date 7 : 08/03/1941     date 8 : 8/1/1980     date 9 : 30/6/1976"""
print(s)
date 0 : 14/9/2000
date 1 : 20/04/1971     date 2 : 14/09/1913     date 3 : 2/3/1978
date 4 : 1/7/1986     date 5 : 7/3/47     date 6 : 15/10/1914
date 7 : 08/03/1941     date 8 : 8/1/1980     date 9 : 30/6/1976

Exemple introductif : Format de date

Le premier chiffre du jour est soit 0, 1, 2, ou 3 ; ceci se traduit par [0-3].

Le second chiffre est compris entre 0 et 9, soit [0-9]. Le format des jours est traduit par [0-3][0-9]. Mais le premier chiffre du jour est facultatif, ce qu’on précise avec le symbole ? : [0-3]?[0-9].

Les mois suivent le même principe : [0-1]?[0-9].

Pour les années, ce sont les deux premiers chiffres qui sont facultatifs, le symbole ? s’appliquent alors sur les deux premiers chiffres : [0-2]?[0-9]?[0-9][0-9]. Le format final d’une date devient alors :

[0-3]?[0-9]/[0-1]?[0-9]/[0-2]?[0-9]?[0-9][0-9]

Le module re gère les expressions régulières, celui-ci traite différemment les parties de l’expression régulière qui sont entre parenthèses de celles qui ne le sont pas : c’est un moyen de dire au module re que nous nous intéressons à telle partie de l’expression qui est signalée entre parenthèses. Comme la partie qui nous intéresse - une date - concerne l’intégralité de l’expression régulière, il faut insérer celle-ci intégralement entre parenthèses.

La première étape consiste à construire l’expression régulière, la seconde à rechercher toutes les fois qu’un morceau de la chaîne s définie plus haut correspond à l’expression régulière.

In [3]:
import re
# première étape : construction
expression = re.compile("([0-3]?[0-9]/[0-1]?[0-9]/[0-2]?[0-9]?[0-9][0-9])")
# seconde étape : recherche
res = expression.findall(s)
print(res)
['14/9/2000', '20/04/1971', '14/09/1913', '2/3/1978', '1/7/1986', '7/3/47', '15/10/1914', '08/03/1941', '8/1/1980', '30/6/1976']

Le résultat est une liste dont chaque élément correspond aux parties comprises entre parenthèses qu’on appelle des groupes.

Lorsque les expressions régulières sont utilisées, on doit d’abord se demander comment définir ce qu’on cherche puis quelles fonctions utiliser pour obtenir les résultats de cette recherche. Les deux paragraphes qui suivent y répondent.

Syntaxe

La syntaxe des expressions régulières est décrite sur le site officiel de python. La page Regular Expression Syntax décrit comment se servir des expressions régulières, les deux pages sont en anglais. Comme toute grammaire, celle des expressions régulières est susceptible d’évoluer au fur et à mesure des versions du langage python.

Les ensembles de caractères

Lors d’une recherche, on s’intéresse aux caractères et souvent aux classes de caractères : on cherche un chiffre, une lettre, un caractère dans un ensemble précis ou un caractère qui n’appartient pas à un ensemble précis. Certains ensembles sont prédéfinis, d’autres doivent être définis à l’aide de crochets.

Pour définir un ensemble de caractères, il faut écrire cet ensemble entre crochets. Par exemple, [0123456789] désigne un chiffre. Comme c’est une séquence de caractères consécutifs, on peut résumer cette écriture en [0-9]. Pour inclure les symboles + et -, il suffit d’écrire : [-0-9+]. La subtilité est qu’il faut penser à mettre le symbole - au début pour éviter qu’il ne désigne une séquence.

Le caractère ^ inséré au début du groupe signifie que le caractère cherché ne doit pas être un de ceux qui suivent. Le tableau suivant décrit les ensembles prédéfinis et leur équivalent en terme d’ensemble de caractères :

  • . désigne tout caractère non spécial quel qu’il soit.
  • \d désigne tout chiffre, est équivalent à [0-9].
  • \D désigne tout caractère différent d’un chiffre, est équivalent à [^0-9].
  • \s désigne tout espace ou caractère approché, est équivalent à [\; \t\n\r\f\v]. Ces caractères sont spéciaux, les plus utilisés sont \t qui est une tabulation, \n qui est une fin de ligne et qui \r qui est un retour à la ligne.
  • \S désigne tout caractère différent d’un espace, est équivalent à [^ \t\n\r\f\v].
  • \w désigne toute lettre ou tout chiffre, est équivalent à [a-zA-Z0-9_].
  • \W désigne tout caractère différent d’une lettre ou d’un chiffre, est équivalent à [^a-zA-Z0-9_].
  • ^ désigne le début d’un mot sauf s’il est placé entre crochets.
  • $ désigne la fin d’un mot sauf s’il est placé entre crochets.

A l’instar des chaînes de caractères, comme le caractère \ est un caractère spécial, il faut le doubler : [\\].

Les multiplicateurs

Les multiplicateurs permettent de définir des expressions régulières comme : un mot entre six et huit lettres qu’on écrira [\w]{6,8}. Le tableau suivant donne la liste des multiplicateurs principaux :

  • * présence de l’ensemble de caractères qui précède entre 0 fois et l’infini
  • + présence de l’ensemble de caractères qui précède entre 1 fois et l’infini
  • ? présence de l’ensemble de caractères qui précède entre 0 et 1 fois
  • {m,n} présence de l’ensemble de caractères qui précède entre m et n fois, si m=n, cette expression peut être résumée par {n}.
  • (?!(...)) absence du groupe désigné par les points de suspensions.

L’algorithme des expressions régulières essaye toujours de faire correspondre le plus grand morceau à l’expression régulière.

Par exemple, pour la chaîne de charactère <h1>mot</h1>, l’expression régulière <.*> correspond à trois morceaux : * <h1> * </h1> * <h1>mot</h1> => le plus grand qui sera choisi.

Pour choisir les plus petits, il faudra écrire les multiplicateurs comme ceci : *?, +?

In [4]:
import re
import re
s = "<h1>mot</h1>"
print(re.compile("<.*>").findall(s))
print(re.compile("<.*?>").findall(s))



print(re.compile("(<.*>)").match(s).groups())
['<h1>mot</h1>']
['<h1>', '</h1>']
('<h1>mot</h1>',)
In [5]:
print(re.compile("(.*</h1>)").match(s))
<re.Match object; span=(0, 12), match='<h1>mot</h1>'>
In [6]:
print(re.compile(".*(<.h1>)").match(s).groups()) 
print(re.compile("(<.?h1>)").findall(s))
('</h1>',)
['<h1>', '</h1>']

Un exemple pour trouver des adresses emails :

In [7]:
text_emails = 'Hello from [email protected] to [email protected] about the meeting @2PM'
In [8]:
# \S` désigne tout caractère différent d'un espace 
# `+` présence de l'ensemble de caractères qui précède entre 1 fois et l'infini
liste_emails = re.findall('\[email protected]\S+', text_emails)     
In [9]:
print(liste_emails)

Exercices

Aller plus loin pour apprendre les regex

  • Le site regex101 permet de tester les expressions régulières utilisées tout en ayant une explication qui accompagne ce test

  • De même pour ce site qui comporte une cheat sheet en bas de la page.

  • Les jeux de Regex Crossword permettent d’apprendre les expressions régulières en s’amusant