logo le blog invivoo blanc

Regex : pourquoi il faut aimer les expressions régulières – Partie 1

21 octobre 2021 | Design & Code | 0 comments

Utilisée à la manière des outils de recherche de texte dans un document, une expression régulière, aussi dénommée « regex » ou « regexp », fournit un moyen concis et flexible pour la correspondance de chaînes de texte, tel que des caractères particuliers, mots ou motifs de caractères.

Écrites dans un langage formel, langage de programmation avec symboles, qui peut être interprété par un processeur d’expression régulière, elles sont des expressions « Joker » intelligentes pour rechercher, isoler ou remplacer des chaînes de texte.

Dans cet article, je vais aborder plusieurs points :

  • Module regex en Python
  • Recherche et export des données
  • Regroupement et groupes nommés
  • Quantificateurs
  • Compilation des regex
  • Caractères et séquences d’échappement
  • Barre oblique inversée

Module regex en Python

Python fournit un module complet permettant l’utilisation des expressions régulières qui est le module « re ». Les fonctions clés de ce module sont :

  • La fonction re.search() qui permet de voir si une chaîne correspond à une expression régulière, semblable à l’utilisation de la méthode find() pour les chaînes de caractères,
  • La fonction re.findall() qui permet d’extraire des fragments d’une chaîne de caractère correspondant à votre expression régulière, semblable à une combinaison de find() et coupe avec les crochets. Sachant qu’une coupe avec les crochets appliquée sur une chaîne de caractères permet d’avoir une sous-chaîne de la chaîne principale. Prenons comme exemple la chaine ‘Magazine Programmez’, si on veut extraire uniquement le nom du magazine, on peut utiliser l’instruction suivante : chaine[9:] qui va retourner la sous-chaine ‘Programmez’.
  • La fonction re.compile() qui permet de compiler une expression régulière et de la réutiliser sans la recréer. 

Avant de commencer les exemples d’utilisation des regex, je vous propose ce guide rapide d’expressions régulières. Ce guide contient les « caractères marqueurs » de base utilisés pour les regex ainsi que leurs traductions.

Recherche des données

Les expressions régulières permettent de faire de la recherche de données. En Python, c’est la fonction re.search() qui permet de faire ce traitement en renvoyant un objet contenant un tuple (span) correspondant à la position du début et la position de fin de la sous-chaîne et la sous-chaîne correspondante à la regex. Si la regex ne correspond à aucune sous-chaîne de la chaîne de caractères, un objet None sera retourné. L’utilisation de cette fonction dans une condition renvoie Vrai/Faux selon si la chaîne correspond à la regex.

Si nous disposons ici de cette chaîne de caractères :

chaine = 'From: Using the : character'

Voici un exemple d’utilisation de la fonction re.search() qui recherche le mot « Using » dans la chaîne ci-dessus :

import re
re.search('Using', chaine)

Le résultat de cette recherche est l’objet suivant :

<_sre.SRE_Match object; span=(6, 11), match='Using'>

Présentons maintenant deux comparaisons entre la fonction re.search() et les fonctions utilisées pour la recherche des données dans les chaînes de caractères find() et startswith().

On veut afficher la chaîne si elle contient le mot « From » avec la fonction find(), qui renvoie un nombre désignant la position du mot dans la chaîne :

if chaine.find('From') >= 0:
    print(chaine)

La version avec les regex :

import re

if re.search('From', chaine):
    print(chaine)

Maintenant, si on veut vérifier que la chaîne commence par le mot « From », on peut utiliser la fonction startswith() pour les chaînes de caractères :

if chaine.startswith('From'):
    print(chaine)

En utilisant le module « re », nous pouvons perfectionner la recherche précédente en ajoutant le caractère spécial « ^ » afin de vérifier que la chaîne commence par le mot « From » :

import re

if re.search('^From', chaine):
    print(chaine)

Dans cet exemple, l’utilisation de la fonction startswith() est plus efficace que l’utilisation d’une expression régulière pour vérifier qu’une chaîne de caractères commence par un tel motif. En effet, la création d’une expression régulière est associée à la création d’une machine à états, ce qui possède un coût. L’utilisation des regex est très utile, mais quand il s’agit de vérifier uniquement qu’une chaîne de caractère commence ou se termine par un motif, l’utilisation des fonctions startswith() et endswith() est recommandée, car elle est moins coûteuse que la création d’une machine à états.

 Il existe aussi une autre fonction de recherche dans le module « re » : la fonction re.match(). À la différence de la fonction re.search() qui analyse la chaîne à la recherche d’une position où la regex correspond, re.match() détermine si la regex correspond dès le début de la chaîne. Si la regex ne correspond pas dès le début de la chaîne, un objet None sera retourné.

L’exécution de ce code :

import re
re.match('From', chaine)

renvoie l’objet suivant :

<_sre.SRE_Match object; span=(0, 4), match='From'>

Il s’agit du même type d’objet renvoyé par la fonction re.search(). Si nous exécutons maintenant ce code :

import re
print(re.match('Using', chaine))

Le résultat retourné est None, car la regex ne correspond pas dès le début de la chaîne. Ce n’est pas le résultat trouvé lorsque nous avons fait cette recherche avec la fonction re.search(), car cette dernière analyse toute la chaîne.

Export des données

Si nous voulons réellement extraire les chaînes correspondantes à notre expression régulière, nous utilisons la fonction re.findall(). Le code suivant montre l’extraction de tous les nombres de la chaîne de caractères x :

import re

x = 'My 2 favorite numbers are 19 and 42'
y = re.findall('[0-9]+',x)
print(y)

L’expression régulière utilisée :

Ce code retourne une liste de plusieurs chaînes secondaires qui correspondent à l’expression régulière, c’est-à-dire tous les chiffres de la chaîne x :

['2', '19', '42']

Si aucune chaîne secondaire de x ne correspond à l’expression régulière, une liste vide sera retournée. Cela est le cas de l’exemple suivant :

y = re.findall('[AEIOU]+',x)
print(y)

Comme il s’agit d’une correspondance sensible à la casse, c’est-à-dire qu’il y a une différence entre les lettres majuscules et minuscules, aucun caractère de la chaîne x ne correspond à aucun caractère de la liste [AEIOU]. D’où ce résultat :[]

Le caractère Joker

Le point « . » est un caractère Joker qui correspond à tout caractère. Si vous ajoutez le symbole astérisque « * », le caractère sera répété « n’importe quel nombre de fois ».

Quantificateurs

Les quantificateurs permettent de répéter un ensemble de caractères plusieurs fois. Deux types de quantificateurs existent :

  • Les quantificateurs explicites dans leur forme qui indiquent le nombre de répétitions et qui peuvent s’écrire de 4 façons
  • Les quantificateurs préconçus utilisant des métacaractères et qui peuvent être associés à des recherches voraces ou non-voraces. Les types de recherche vorace et non-vorace sont définis et détaillés dans la partie suivante. Voici la liste du deuxième type de quantificateurs :

Voici un exemple de vérification d’un numéro de téléphone qui utilise ces deux types de quantificateurs :

re.match('^0[0-9]([.s]?[0-9]{2}){4}', '06 98 65 22 76')

Dans la regex de cet exemple, nous avons utilisé le quantificateur « ? » pour indiquer la répétition de l’un des caractères « . » ou espace 0 ou 1 seule fois. Nous avons indiqué le nombre 4 de répétitions de 2 chiffres précédés d’un espace ou d’un point ou de rien. Donc, l’exemple du numéro de téléphone mentionné correspond bien à la regex, d’où le résultat :

<_sre.SRE_Match object; span=(0, 14), match='06 98 65 22 76'>

Les numéros de téléphone 06.88.56.12.66 et 0789443899 correspondent aussi à la regex utilisée.

Recherche vorace

Les caractères « * » et « + » répétés déplacent vers l’extérieur dans les deux sens (vorace) afin de correspondre à la plus grande chaîne possible. L’exemple suivant illustre ce type de recherche :

chaine = 'From: Using the : character'
y = re.findall('^F.+:', chaine)
print(y)

Dans cet exemple, nous faisons une extraction de tout le texte qui précède le caractère « : » en utilisant l’expression régulière suivante :

Comme nous avons deux fois le caractère « : » dans la chaîne et comme il s’agit d’une recherche vorace, la recherche s’étend jusqu’au dernier caractère « : » de la chaîne tant que la regex est vérifiée. D’où ce résultat :

['From: Using the :']

Recherche non vorace

Les regex qui répètent du code ne sont pas toutes voraces !  Si vous ajoutez le caractère « ? », les caractères « + » et « * » se détendent un peu…

Pour illustrer ce type de recherche, reprenons l’exemple ci-dessus pour la recherche vorace et adaptons-le afin d’extraire uniquement le texte précédant la première apparition du caractère « : ».

chaine = 'From: Using the : character'
y = re.findall('^F.+?:', chaine)
print(y)

Nous avons rajouté le caractère « ? » après le « .+ » afin de s’arrêter à la première apparition du caractère « : »

D’où le résultat suivant :

['From:']

Amélioration de l’extraction de données

Nous avons vu plus haut, comment extraire des données avec les expressions régulières. Nous pouvons affiner notre recherche avec re.findall() et déterminer séparément quelle portion de la chaîne de caractères est à extraire en utilisant des parenthèses dans la regex. Les parenthèses ne font pas partie de la recherche, mais elles précisent où commence et où se termine la chaîne à extraire.

Voici un exemple qui consiste à extraire une adresse mail (adresse mail de l’expéditeur) d’une chaîne de caractère.

La chaîne que nous allons traiter :

chr = 'From marwa.thlithi@invivoo.com Sat Jan 5 09:14:16 20'

Le code permettant d’extraire l’adresse mail :

y = re.findall('S+@S+',chr)
print(y)

Cette expression régulière permet d’extraire l’adresse mail :

Le résultat est :

['marwa.thlithi@invivoo.com']

Imaginons maintenant que nous avons une autre adresse mail dans notre chaîne de caractères (adresse mail du destinataire) et que nous voulons extraire uniquement l’adresse mail de l’expéditeur.

Voici la nouvelle chaîne que nous allons traiter :

chr = 'From marwa.thlithi@invivoo.com Sat Jan 5 09:14:16 20 to admin@invivoo.com'

Si nous appliquons le code et la regex précédents, nous obtenons le résultat suivant :

['marwa.thlithi@invivoo.com','admin@invivoo.com']

Cela ne correspond pas au résultat attendu. Afin de pouvoir extraire uniquement l’adresse mail de l’expéditeur, il faut affiner notre recherche et être plus précis dans notre extraction. Pour cela, nous pouvons utiliser cette proposition d’expression régulière :

Nous pouvons utiliser aussi l’expression régulière suivante :

NB : Dans la deuxième proposition de regex, il faut faire attention à ne pas oublier le caractère « ? », car si on l’oublie, on aura une recherche vorace qui ne donnera pas le résultat attendu qui est l’adresse mail de l’expéditeur, mais plutôt la dernière adresse mail dans la chaîne de caractère.

Retrouvez la deuxième partie de cet article la semaine prochaine sur notre blog ! Vous pouvez aussi découvrir notre expertise Python pour être accompagné par nos spécialistes.