Les expression régulières sont des filtres de recherche de texte qui suivent des règles très précises. Elles sont disponibles dans de nombreux langages et vous les entendrez souvent être mentionnées par les termes 'regex
' ou 'regexp
'. Les expressions régulières couvrent un large spectre de règles, recherche de répétitions, de texte spécifique et bien d'autres choses. C'est par exemple très utile pour extraire les majuscules d'un texte, ou encore retrouver un numéro de téléphone dans un document.
Quand vous progresserez en Python, vous réaliserez que de nombreux problèmes de 'parsing', d'analyse de texte, peuvent être résolus par des expressions régulières.
C'est aussi une question classique en entretien d'embauche... Parce qu'elles sont célèbres pour leur syntaxe assez étrange. C'est le prix à payer pour leur extrème flexibilité.
Si vous les avez déjà pratiquées dans un autre langage, comme Perl, vous remarquerez que leur syntaxe d'utilisation est très similaire en Python.
Nous allons maintenant les découvrir à l'aide du module re
.
En avant la musique !
Imaginons que nous avons une chaine de caractères comme suit :
texte = "Le numéro de téléphone de cette personne st 06 01 02 03 04. Appelez vite !"
On va commencer par vérifier que la chaine de caractères "téléphone" est dans le texte. Cela peut se faire très simplement et très rapidement :
'téléphone' in texte
True
Voyons maintenant comment le faire avec une expression régulière, parce que nous allons par la suite utiliser des modèles beaucoup plus complexes.
import re
motclef = 'téléphone'
re.search(motclef, texte)
<re.Match object; span=(13, 22), match='téléphone'>
motclef = 'absent du texte'
re.search(motclef, texte)
Maintenant, nous avons vu que re.search () prend le mot clef cherché, scanne le texte, puis retournerait un objet Match. Si aucun modèle n'est trouvé, un None est renvoyé (dans Jupyter Notebook, cela signifie simplement que rien n'est sorti sous la cellule).
Examinons de plus près cet objet Match.
motclef = 'téléphone'
match = re.search(motclef, texte)
match
<re.Match object; span=(13, 22), match='téléphone'>
Notez les valeurs de span, qui donnent le début et la fin de la position du mot clef.
match.span()
(13, 22)
match.start()
13
match.end()
22
Que se passe-t-il si le mot clef se trouve plusieurs fois dans le texte ?
texte = 'Mon téléphone est un nouveau téléphone'
match = re.search("téléphone", texte)
match.span()
(4, 13)
Notez que seule la première apparition est relevée. Pour obtenir la liste des occurences, il faut utiliser la méthode .findall()
occurences = re.findall("téléphone", texte)
occurences
['téléphone', 'téléphone']
len(occurences)
2
Pour obtenir la liste des objets match, des occurences, il faut utiliser un itérateur :
for match in re.finditer("téléphone",texte):
print(match.span())
(4, 13) (29, 38)
Pour retrouver le mot clef cherché, il faut utilsier la méthode .group()
match.group()
'téléphone'
Jusqu'à présent, nous avons appris à rechercher un mot clef simple, une chaîne de caractères basique. Qu'en est-il avec des exemples plus complexes ? Telles que d'essayer de trouver un numéro de téléphone dans une grande chaîne de texte ? Ou une adresse e-mail ?
Nous pourrions simplement utiliser la méthode de recherche si nous connaissons le téléphone ou l'e-mail exact, mais que faire si nous ne le savons pas ? Nous pouvons connaître le format général, et nous pouvons l'utiliser avec des expressions régulières pour rechercher dans le document des chaînes qui correspondent à un modèle particulier.
C'est là que la syntaxe peut sembler étrange au début, mais prenez votre temps avec cela, souvent c'est juste une question de recherche du code de modèle.
Nous allons reprendre notre exemple de base et ensuite l'étendre à des choses plus complexes.
Commençons!
Une des utilisations les plus courantes du module re
est de rechercher des bouts de texte.
Voici un exemple :
import re
# Liste des mots recherchés
aiguilles = [ 'terme1', 'terme2' ]
# Texte à analyser
botte = "Ceci est une chaine avec le terme1, mais elle ne contient pas l'autre terme."
for aiguille in aiguilles:
print ('Recherche de "%s" dans: \n"%s"' % (aiguille, botte))
# Recherche de l'aiguille dans la botte de foin
if re.search(aiguille, botte):
print('\nLe terme est présent. \n')
else:
print('\nLe terme est absent.\n')
Recherche de "terme1" dans: "Ceci est une chaine avec le terme1, mais elle ne contient pas l'autre terme." Le terme est présent. Recherche de "terme2" dans: "Ceci est une chaine avec le terme1, mais elle ne contient pas l'autre terme." Le terme est absent.
Nous venons de voir que re.search() va prendre le modèle, analyser le texte, et enfin renvoyer un objet Match s'il est trouvé. Si le modèle n'est pas trouvé, None sera renvoyé.
Pour bien comprendre cela, nous allons décortiquer l'exemple précédent : To give a clearer picture of this match object, check out the cell below:
# Le mot recherché
aiguille = 'terme2'
# Texte à analyser
botte = "Ceci est une chaine avec le terme1, mais elle ne contient pas l'autre terme."
match = re.search(aiguille, botte)
type(match)
NoneType
# Le mot recherché
aiguille = 'terme1'
# Texte à analyser
botte = "Ceci est une chaine avec le terme1, mais elle ne contient pas l'autre terme."
match = re.search(aiguille, botte)
type(match)
_sre.SRE_Match
Cet objet Match renvoyé par la méthode search()
est plus qu'un Booléen, en cas de correspondance il contiendra des informations sur celle-ci, il y a la chaine testée, l'expression régulire utiulisée et la position à laquelle a été découverte la correspondance.
Voyons maintenant les méthodes que l'on peut utiliser sur un objet Match :
# Afficher le début de la correspondance
match.start()
28
# Afficher la fin
match.end()
34
Le module re et une syntaxe particulière permettent de couper une chaine, à la manière de ce que nous avons vu avec la méthode split()
pour les chaines de caractères.
# Terme ou caractère pour faire la coupe
charniere = '@'
phrase = "Quel est le domaine de quelqu'un dont l'adresse mail est : hello@gmail.com"
# Couper la phrase
re.split(charniere,phrase)
["Quel est le domaine de quelqu'un dont l'adreese mail est : hello", 'gmail.com']
Soyez sur d'avoir noté que re.split() retourne une liste de laquelle les termes pour faire la coupe ont disparu, et les éléments de la liste dont les coupes de la chaine de départ. Pratiquez avec vos propres exemple pour être sur de bien maitriser le concept !
Vous pouvez aussi utiliser re.findall() pour trouver toutes les occurences d'un modèle dans une chaine.
Exemple :
# Renvoie la liste de toutes les occurences
re.findall('match','phrase de test avec match qui se trouve au milieu')
['match']
re
¶C'est le gros morceau de cette leçon sur l'utilisation de re
avec Python. Les expressions régulières supportent une grande variété de modèles plus puissants que la simple recherche de la position d'une chaine de caractère dans une autre.
Il est possible d'utiliser des meta-caractères avec re
pour rechercher des modèles très spécifiques.
Comme nous allons faire de nombreux essais avec différentes syntaxes, commençont par définir une fonction pour afficher les résultats d'une recherche passée en paramètre.
def multi_re(aiguilles,phrase):
'''
Prend une liste de modèles regex en entrée
Affiche la liste de toutes les occurences
'''
for aiguille in aiguilles:
print ('Recherche dans une phrase en utilisant le modèle re : %r' %aiguille)
print (re.findall(aiguille,phrase))
print ('\n')
Il y a 5 façons différentes de rechercher une répétition :
1.) Un modèle suivi du meta-charactère * est répété zéro fois ou plusieurs.
2.) Remplacer * par + et le modèle devra apparaitre au moins une fois.
3.) Utiliser ? signifie que le modèle apparait zéro ou une fois.
4.) Un nombre précis de répétitions sera trouvé avec la syntaxe {m} à la suite du modèle, où m représente le nombre attendu de répétitions du modèle.
5.) La syntaxe {m,n} permet de spécifier un nombre minimum (m) et une nombre maximum (n) de répétitions. La notation {m,} sans préciser n signifie que la valeur apparait au moins m fois, sans maximum.
Voyons quelques exemples de tout cela avec notre fonction multi_re définie précédemment :
phrase_test = 'sdsd..sssddd...sdddsddd...dsds...dsssss...sdddd'
aiguilles = [ 'sd*', # s suivi par aucun ou plusieurs d
'sd+', # s suivi par un ou plusieurs d
'sd?', # s suivi par aucun ou un seul d
'sd{3}', # s suivi par trois d
'sd{2,3}', # s suivi par deux à trois d
]
multi_re(aiguilles,phrase_test)
Recherche dans une phrase en utilisant le modèle re : 'sd*' ['sd', 'sd', 's', 's', 'sddd', 'sddd', 'sddd', 'sd', 's', 's', 's', 's', 's', 's', 'sdddd'] Recherche dans une phrase en utilisant le modèle re : 'sd+' ['sd', 'sd', 'sddd', 'sddd', 'sddd', 'sd', 'sdddd'] Recherche dans une phrase en utilisant le modèle re : 'sd?' ['sd', 'sd', 's', 's', 'sd', 'sd', 'sd', 'sd', 's', 's', 's', 's', 's', 's', 'sd'] Recherche dans une phrase en utilisant le modèle re : 'sd{3}' ['sddd', 'sddd', 'sddd', 'sddd'] Recherche dans une phrase en utilisant le modèle re : 'sd{2,3}' ['sddd', 'sddd', 'sddd', 'sddd']
Des crochets [ et ] sont utilisés pour construire un modèle pour trouver un groupe de caractères spécifiques. Par exemple, l'entrée [ab] cherchera des occurences de a ou b.
Voyons quelques exemples :
phrase_test = 'sdsd..sssddd...sdddsddd...dsds...dsssss...sdddd'
aiguilles = [ '[sd]', # s ou d
's[sd]+'] # s suivi par un ou plusieurs s ou d
multi_re(aiguilles,phrase_test)
Recherche dans une phrase en utilisant le modèle re : '[sd]' ['s', 'd', 's', 'd', 's', 's', 's', 'd', 'd', 'd', 's', 'd', 'd', 'd', 's', 'd', 'd', 'd', 'd', 's', 'd', 's', 'd', 's', 's', 's', 's', 's', 's', 'd', 'd', 'd', 'd'] Recherche dans une phrase en utilisant le modèle re : 's[sd]+' ['sdsd', 'sssddd', 'sdddsddd', 'sds', 'sssss', 'sdddd']
Bien entendu, le premier [sd]
va renvoyer toutes les occurences.
En revanche le second s[sd]+
ne va renvoyer que ceux qui commencent par un s dans la phrase de test en entrée.
On utilise le caractère ^ pour exlure des termes en le combinant avec la notation en crochets [] vue précedement.
Par exemple [^...]
va rechercher tous les caractères qui ne sont pas mentionnés entre les crochets.
Voyons quelques exemples :
phrase_de_test = 'Ceci est une chaine ! Avec de la ponctuation. Comment la supprimer ?'
Il suffit d'utiliser [^!.? ] pour retrouver tout ce qui n'est pas un ! ou un . ou un ? ou encore un espace.
On ajoute un + pour s'assurer que l'on retrouvera toutes les occurences, pas uniquement la première et le tour est joué.
Cela reviendra à retrouver les mots individuellement.
re.findall('[^!.? ]+',phrase_de_test)
['Ceci', 'est', 'une', 'chaine', 'Avec', 'de', 'la', 'ponctuation', 'Comment', 'la', 'supprimer']
propre = ' '.join(re.findall('[^!.? ]+',phrase_de_test))
propre
'Ceci est une chaine Avec de la ponctuation Comment la supprimer'
Autre exemple pour masquer des chiffres
phrase_de_test = "Il y a 3 nombres 34 placés au milieu 5 de cette phrase"
Pour masquer les nombres on va utiliser ^\d et on ajoute un + pour garder les mots ensemble
re.findall(r'[^\d]+',phrase_de_test)
['Il y a ', ' nombres ', ' placés au milieu ', ' de cette phrase']
Il peut devenir compliqué de taper toute l a list des caractères à chercher. Il y a donc une version plus compacte qui permet de donner des plages de caractères. Il suffit pour cela de donner le caractère de début et celui de fin de la plage à utiliser.
Le format est [début-fin]
.
Traditionnellement, cette syntaxe est utilisée pour faire une recherche d'une plage de caractères spécifiques de l'alphabet. Par exemple [a-f]
permet de retrouver les apparitions de toute lettre entre a et f.
Voyons quelques exemples :
phrase_de_test = 'Ceci est une phrase de test. Voyons ce qui peut être trouvé comme lettres.'
recherche=[ '[a-z]+', # lettres minuscules
'[A-Z]+', # lettres majuscules
'[a-zA-Z]+', # lettres minuscules ou majuscules
'[A-Z][a-z]+'] # une lettre majuscule suivie par des lettres minuscules
multi_re(recherche,phrase_de_test)
Recherche dans une phrase en utilisant le modèle re : '[a-z]+' ['eci', 'est', 'une', 'phrase', 'de', 'test', 'oyons', 'ce', 'qui', 'peut', 'tre', 'trouv', 'comme', 'lettres'] Recherche dans une phrase en utilisant le modèle re : '[A-Z]+' ['C', 'V'] Recherche dans une phrase en utilisant le modèle re : '[a-zA-Z]+' ['Ceci', 'est', 'une', 'phrase', 'de', 'test', 'Voyons', 'ce', 'qui', 'peut', 'tre', 'trouv', 'comme', 'lettres'] Recherche dans une phrase en utilisant le modèle re : '[A-Z][a-z]+' ['Ceci', 'Voyons']
On peut utiliseer des codes particuliers pour identifier des types de caractères dans les modèles.
Des caractères tels qu'un chiffre ou une chaîne ont des codes spécifiques pour les représenter. Vous pouvez les utiliser pour créer une chaîne de caractères 'modèle'.
Remarquez qu'ils font un usage intensif de la barre oblique vers l'arrière . Pour cette raison, lors de la définition d'une chaîne de modèle pour consrtuire une expression régulière, nous utiliserons le format :
r'modèle'
Le r devant la chaîne permet à Python de comprendre que le \ dans la chaîne de modèle n'est pas une barre oblique d'échappement comme habituellement.
Ci-dessous, vous trouverez un tableau de tous les codes possibles:
Code | Description | Exemple de modèle | Exemple d'occurence |
---|---|---|---|
\d | un chiffre | fichier_\d\d | fichier_25 |
\D | Caractère non numérique | \D\D\D | ABC |
\s | espace, tabulation, etc.) | a\sb\sc | a b c |
\S | Pas d'espace | \S\S\S\S | Yoyo |
\w | alphanumérique | \w-\w\w\w | A-b_1 |
\W | non alphanumérique | \W\W\W\W\W | *-+=) |
phrase_de_test = 'Voici un texte avec quelques chiffres 1233 et un symbole #hashtag'
modele =[ r'\d+', # séquence de chiffres
r'\D+', # séquence de caractères non numériques
r'\s+', # séquence d'espaces
r'\S+', # séquence de non espaces
r'\w+', # caractères alphanumériques
r'\W+', # caractères non alphanumériques
]
multi_re(modele,phrase_de_test)
Recherche dans une phrase en utilisant le modèle re : '\\d+' ['1233'] Recherche dans une phrase en utilisant le modèle re : '\\D+' ['Voici un texte avec quelques chiffres ', ' et un symbole #hashtag'] Recherche dans une phrase en utilisant le modèle re : '\\s+' [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '] Recherche dans une phrase en utilisant le modèle re : '\\S+' ['Voici', 'un', 'texte', 'avec', 'quelques', 'chiffres', '1233', 'et', 'un', 'symbole', '#hashtag'] Recherche dans une phrase en utilisant le modèle re : '\\w+' ['Voici', 'un', 'texte', 'avec', 'quelques', 'chiffres', '1233', 'et', 'un', 'symbole', 'hashtag'] Recherche dans une phrase en utilisant le modèle re : '\\W+' [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' #']
Autre exemple pour un numéro de téléphone :
texte = "Mon téléphone est le 0033-06-01-02-03-04"
tel = re.search(r'\d\d\d\d-\d\d-\d\d-\d\d-\d\d-\d\d',texte)
tel.group()
'0033-06-01-02-03-04'
Vous avez remarqué qu'il faut dupliquer les \d, ce qui est peu lisible et agaçant. On peut faire mieux en utilisant des quantificateurs.
Maintenant que nous connaissons les désignations de caractères spéciaux, nous pouvons les utiliser avec des multiplicateurs pour définir le nombre d'occurence et le modèle que nous attendons.
Code | Description | Exemple de Code | Exammple de Match |
---|---|---|---|
+ | Apparait 1 fois ou plus | Version \w-\w+ | Version A-b1_1 |
{3} | Apparait exactement 3 fois | \D{3} | abc |
{2,4} | Apparait de 2 à 4 fois | \d{2,4} | 123 |
{3,} | Apparait 3 fois ou plus | \w{3,} | anycharacters |
\* | plusieurs caracctères ciblés | A\*B\*C* | AAACC |
? | 1 caractère ciblé | plural? | plurals |
re.search(r'\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}', texte)
<re.Match object; span=(21, 40), match='0033-06-01-02-03-04'>
Et si nous voulions faire deux tâches, trouver des numéros de téléphone, mais aussi pouvoir extraire rapidement leur code pays. Nous pouvons utiliser des groupes pour toute tâche générale qui implique le regroupement d'expressions régulières (afin de pouvoir les décomposer plus tard).
En utilisant l'exemple du numéro de téléphone, nous pouvons séparer des groupes d'expressions régulières à l'aide de parenthèses:
modele_tel = re.compile(r'(\d{4})-(\d{2})-(\d{2}-\d{2}-\d{2}-\d{2})')
resultat = re.search(modele_tel, texte)
resultat.group()
'0033-06-01-02-03-04'
On peut aussi décortiquer le résultat par groupe, identifé par les parenthèses :
resultat.group(1)
'0033'
resultat.group(2)
'06'
resultat.group(3)
'01-02-03-04'
Il n'y a que 3 groupes
resultat.group(4)
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-71-f0bf4e8ee075> in <module> ----> 1 resultat.group(4) IndexError: no such group
re.search(r'homme|femme','Cet homme se trouvait là')
<re.Match object; span=(4, 9), match='homme'>
re.search(r'homme|femme','Cette femme se trouvait là')
<re.Match object; span=(6, 11), match='femme'>
(Il semblerait que la traduction officielle de 'wildcard' soit métacaractère, on est d'accord que générique est plus compréhensible).
On peut utiliser un caractère générique pour signifier qu'on ne cherche pas un caractère particulier et que n'importe lequel fera l'affaire. Il suffit d'un point . pour cela
re.findall(r'.at', "Le chat grimpe après le rat tout en haut du mat tout plat.")
['hat', 'rat', 'mat', 'lat']
On n'a retrouvé que 3 lettres, en effet le . ne remplace qu'une seule lettre. On peut en placer plusieurs ou utiliser les multiplicateurs
re.findall(r'...at', "Le chat grimpe après le rat tout en haut du mat tout plat.")
[' chat', 'e rat', 'u mat', ' plat']
Maintenant on a un problème, certains résultats débordent. On veut les mots qui finissent par at.
Pour cela on va faire :
re.findall(r'\S+at', "Le chat grimpe après le rat tout en haut du mat tout plat.")
['chat', 'rat', 'mat', 'plat']
On utilise ^ pour spécifier le début et $ pour la fin
# Termine par un chiffre
re.findall(r'\d$','Ceci termine par X02')
['2']
# commence avec un chiffre
re.findall(r'^\d','1 se sent bien seul')
['1']
Remarquez que cela prend en compte toute la chaine, pas juste un mot
Beau travail, comme je vous le disais en introduction, les expressions régulières sont un domaine très vaste, n'hésitez pas à consulter la documentation pour apprendre toutes les ficèles.