Les expressions régulières en python

Les expressions régulières sont utilisées dans quasiment tous les langages. C'est un outil très puissant qui permet de vérifier si le contenu d'une variable a la forme de ce que l'on attend. Par exemple si on récupère un numéro de téléphone, on s'attend à ce que la variable soit composée de nombres et d'espaces (ou de tiret) mais rien de plus. Les expressions régulières permettent non seulement de vous avertir d'un caractère non désiré mais également de supprimer/modifier tous ceux qui ne sont pas désirables.

Les bases

On utilise des symboles qui ont une signification:

. ^ $ * + ? { } [ ] \ | ( )
.     Le point correspond à n'importe quel caractère.
^     Indique un commencement de segment mais signifie aussi "contraire de"
$     Fin de segment
[xy]  Une liste de segment possibble. Exemple [abc] équivaut à : a, b ou c
(x|y) Indique un choix multiple type (ps|ump) équivaut à "ps" OU "UMP" 
\d    le segment est composé uniquement de chiffre, ce qui équivaut à [0-9].
\D    le segment n'est pas composé de chiffre, ce qui équivaut à [^0-9].
\s    Un espace, ce qui équivaut à [ \t\n\r\f\v].
\S    Pas d'espace, ce qui équivaut à [^ \t\n\r\f\v].
\w    Présence alphanumérique, ce qui équivaut à [a-zA-Z0-9_].
\W    Pas de présence alphanumérique [^a-zA-Z0-9_].
\     Est un caractère d'échappement

Il est possible de d'imposer le nombre d'occurences avec la syntaxe suivante:

A{2}     : on attend à ce que la lettre A (en majuscule) se répète 2 fois consécutives.
BA{1,9}  : on attend à ce que le segment BA se répète de 1 à 9 fois consécutives.
BRA{,10} : on attend à ce que le segment BRA ne soit pas présent du tout ou présent jusqu'à 10 fois consécutives.
VO{1,}   : on attend à ce que le segment VO soit présent au mois une fois.

SymboleNb Caractères attendusExempleCas possibles
?0 ou 1GR(.)?SGRS, GROS, GRIS, GRAS
+1 ou plusGR(.)+SGROS, GRIS, GRAS
*0, 1 ou plusGR(.)*SGRS,GROO,GRIIIS,GROlivierS

On va abbrégé le plus rapidement possible tout cours théorique, la programmation c'est amusant quand c'est concret.

Prenons ce tutoriel comme un jeu: le but du jeu c'est d'anticiper si une expression est TRUE ou FALSE tout simplement.

La bibliothèque re

Lancez votre interpréteur python et importez la bibliothèque re.

>>> import re

Puis testons une expression:

>>> print re.match(r"GR(.)?S", "GRIS")
<_sre.SRE_Match object at 0x7f37acd2c558>

Si la réponse n'est pas None c'est que le match correspond.

Exercice expressions régulières

On va pas se mentir, pour maitriser les expressions régulières il faut travailler avec.

Tout d'abord préparons les fondamentaux:

Petit exercice

Voici un petit exercice où vous devez déviner si le match correspond ou pas.

EXPRESSION CHAINE TRUE FALSE SOLUTION
GR(.)+S GRIS
GR(.)?S GRS
GRA(.)?S GRAS
GAS(.)? GRAS
GR(A)?S GRAS
GR(A)?S GRS
M(.)+N MAISON
M(.)+(O)+N MAISON
M(.)+([a-z])+N MAISON
M(.)+([A-Z])+N MAISON
^! !MAISON!
!MAISON !MAISON!
^!MAISO!$ !MAISON!
^!MAISON!$ !MAISON!
^!M(.)+!$ !MAISON!
([0-9 ]) 03 88 00 00 00
^0[0-9]([ .-/]?[0-9]{2}){4} 03 88 00 00 00
^0[0-9]([ .-/]?[0-9]{2}){4} 03/88/00/00/00
^0[0-9]([ .-/]?[0-9]{2}){4} 03_88_00_00_00

Chercher une expression

Le match est très intéressant pour valider l'intégrité d'une variable, mais il est également possible de chercher des expressions spécifiques dans une chaine de caractères.

>>> import re
>>> re.findall("([0-9]+)", "Bonjour 111 Aurevoir 222")
['111', '222']

Il est également possible de chercher par groupe:

>>> import re
>>> m = re.search(r"Bienvenue chez (?P<chezqui>\w+) ! Tu as (?P<age>\d+) ans ?", "Bienvenue chez olivier ! Tu as 32 ans")
>>> if m is not None:
...     print m.group('chezqui')
...     print m.group('age')
... 
olivier
32

Remplacer une expression

Pour remplacer une expression on utilise la méthode sub().

>>> print re.sub(r"Bienvenue chez (?P<chezqui>\w+) ! Tu as (?P<age>\d+) ans ?", r"\g<chezqui> a \g<age> ans", "Bienvenue chez olivier ! Tu as 32 ans")
olivier a 32 ans

Le remplacement d'expression se fait sur tous les matchs possibles:

>>> data = """
... olivier;engel;30ans;
... bruce;wayne;45ans;
... """
>>> print re.sub(r"(?P<prenom>\w+);(?P<nom>\w+);(?P<age>\w+);", r"\g<prenom>,\g<nom>,\g<age> ", data)

olivier,engel,30ans 
bruce,wayne,45ans

Compiler une expression

Si vous êtes amenés à utiliser plusieurs fois la même expression (par exemple dans une boucle), vous pouvez la compiler pour gagner en performence

>>> mails = ["olivier@mailbidon.com", "olivier@mailbidon.ca", "8@mailbidon.com", "@mailbidon.com", "olivier@mailbidon"]
>>> regex = re.compile(r"^[a-z0-9._-]+@[a-z0-9._-]+\.[(com|fr)]+"
>>> for mail in mails:
...     if regex.match(mail) is not None:
...             print "Ce mail : %s est valide" % mail   
...     else:
...             print "Erreur ce mail : %s est non valide" % mail  
... 
Ce mail : olivier@mailbidon.com est valide
Erreur ce mail : olivier@mailbidon.ca est non valide
Ce mail : 8@mailbidon.com est valide
Erreur ce mail : @mailbidon.com est non valide
Erreur ce mail : olivier@mailbidon est non valide

Synthèse exercice: créer une expression qui reconnait un mail

Dans de nombreux tutoriels le cas de l'adresse mail est utilisé puisqu'il est à la fois souvent utilisé par les développeurs et assez complexe / complet

Lorsque vous commencez à rédiger une expression régulière, il ne faut pas être très ambitieux, il faut toujours commencer petit, construire brique par brique.

On plante le décors:

#!/usr/bin/python2.7
#-*- coding: utf-8 -*-

import re

string = "TEST" 
regexp = r"(TEST)"

if re.match(regexp, string) is not None:
    print "TRUE"
else:
    print "FALSE"

print re.search(regexp, string).groups()

Si vous exécutez ce script, TRUE et "TEST" seront affichés. Cela permet de ne pas commencer de zéro. L'idée est de suivre pas par pas l'évolution de notre expression régulière.

Une adresse mail en gros ressemble à ça XXXXXXX@XXXXX.COM

Commençons par le début, recherchons XXXXXXXX@, cela peut se traduire par ^[a-z0-9._-]+@

#!/usr/bin/python2.7
#-*- coding: utf-8 -*-

import re

string = "olivier@mailbidon.com" 
regexp = r"(^[a-z0-9._-]+@)"

if re.match(regexp, string) is not None:
    print "TRUE"
else:
    print "FALSE"

print re.search(regexp, string).groups()

Si vous exécutez ce script, TRUE et "olivier@" seront affichés. On est sur la bonne voie! Continuons avec [a-z0-9._-]+\.[(com|fr)]+ puis testons.

#!/usr/bin/python2.7
#-*- coding: utf-8 -*-

import re

string = "olivier@mailbidon.com" 
regexp = r"(^[a-z0-9._-]+@[a-z0-9._-]+\.[(com|fr)]+)"

if re.match(regexp, string) is not None:
    print "TRUE"
else:
    print "FALSE"

print re.search(regexp, string).groups()
Et voila, le résultat devrait être bon. Vous pouvez enlever les paranthèses qui servent de captures d'expressions.