Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Ce cours existe en eBook.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !
Apprenez à programmer en Python

Apprenez à programmer en Python

Mis à jour le lundi 8 septembre 2014
  • 4 semaines
  • Facile

Dans ce chapitre, je vais m'attarder sur les expressions régulières et sur le module re qui permet de les manipuler. En quelques mots, sachez que les expressions régulières permettent de réaliser très rapidement et facilement des recherches sur des chaînes de caractères.

Il existe, naturellement, bien d'autres modules permettant de manipuler du texte. C'est toutefois sur celui-ci que je vais m'attarder aujourd'hui, tout en vous donnant les moyens d'aller plus loin si vous le désirez.

Que sont les expressions régulières ?

Les expressions régulières sont un puissant moyen de rechercher et d'isoler des expressions d'une chaîne de caractères.

Pour simplifier, imaginez que vous faites un programme qui demande un certain nombre d'informations à l'utilisateur afin de les stocker dans un fichier. Lui demander son nom, son prénom et quelques autres informations, ce n'est pas bien difficile : on va utiliser la fonction input et récupérer le résultat. Jusqu'ici, rien de nouveau.

Mais si on demande à l'utilisateur de fournir un numéro de téléphone ? Qu'est-ce qui l'empêche de taper n'importe quoi ? Si on lui demande de fournir une adresse e-mail et qu'il tape quelque chose d'invalide, par exemple « je_te_donnerai_pas_mon_email », que va-t-il se passer si l'on souhaite envoyer automatiquement un email à cette personne ?

Si ce cas n'est pas géré, vous risquez d'avoir un problème. Les expressions régulières sont un moyen de rechercher, d'isoler ou de remplacer des expressions dans une chaîne. Ici, elles nous permettraient de vérifier que le numéro de téléphone saisi compte bien dix chiffres, qu'il commence par un 0 et qu'il compte éventuellement des séparateurs tous les deux chiffres. Si ce n'est pas le cas, on demande à l'utilisateur de le saisir à nouveau.

Quelques éléments de syntaxe pour les expressions régulières

Si vous connaissez déjà les expressions régulières et leur syntaxe, vous pouvez passer directement à la section consacrée au module re. Sinon, sachez que je ne pourrai vous présenter que brièvement les expressions régulières. C'est un sujet très vaste, qui mérite un livre à lui tout seul. Ne paniquez pas, toutefois, je vais vous donner quelques exemples concrets et vous pourrez toujours trouvez des explications plus approfondies de par le Web.

Concrètement, comment cela se présente-t-il ?

Le module re, que nous allons découvrir un peu plus loin, nous permet de faire des recherches très précises dans des chaînes de caractères et de remplacer des éléments de nos chaînes, le tout en fonction de critères particuliers. Ces critères, ce sont nos expressions régulières. Pour nous, elles se présentent sous la forme de chaînes de caractères. Les expressions régulières deviennent assez rapidement difficiles à lire mais ne vous en faites pas : nous allons y aller petit à petit.

Des caractères ordinaires

Quand on forme une expression régulière, on peut utiliser des caractères spéciaux et d'autres qui ne le sont pas. Par exemple, si nous recherchons le mot chat dans notre chaîne, nous pouvons écrire comme expression régulière la chaîne « chat ». Jusque là, rien de très compliqué.

Mais vous vous doutez bien que les expressions régulières ne se limitent pas à ce type de recherche extrêmement simple, sans quoi les méthodes find et replace de la classe str auraient suffit.

Rechercher au début ou à la fin de la chaîne

Vous pouvez rechercher au début de la chaîne en plaçant en tête de votre regex (abréviation de Regular Expression) le signe d'accent circonflexe ^. Si, par exemple, vous voulez rechercher la syllabe cha en début de votre chaîne, vous écrirez donc l'expression ^cha. Cette expression sera trouvée dans la chaîne 'chaton' mais pas dans la chaîne 'achat'.

Pour matérialiser la fin de la chaîne, vous utiliserez le signe $. Ainsi, l'expression q$ sera trouvée uniquement si votre chaîne se termine par la lettre q minuscule.

Contrôler le nombre d'occurences

Les caractères spéciaux que nous allons découvrir permettent de contrôler le nombre de fois où notre expression apparaît dans notre chaîne.

Regardez l'exemple ci-dessous :

chat*

Nous avons rajouté un astérisque (*) après le caractère t de chat. Cela signifie que notre lettre t pourra se retrouver 0, 1, 2, … fois dans notre chaîne. Autrement dit, notre expression chat* sera trouvée dans les chaînes suivantes : 'chat', 'chaton', 'chateau', 'herbe à chat', 'chapeau', 'chatterton', 'chattttttttt'

Regardez un à un les exemples ci-dessus pour vérifier que vous les comprenez bien. On trouvera dans chacune de ces chaînes l'expression régulière chat*. Traduite en français, cette expression signifie : « on recherche une lettre c suivie d'une lettre h suivie d'une lettre a suivie, éventuellement, d'une lettre t qu'on peut trouver zéro, une ou plusieurs fois ». Peu importe que ces lettres soient trouvées au début, à la fin ou au milieu de la chaîne.

Un autre exemple ? Considérez l'expression régulière ci-dessous et essayez de la comprendre :

bat*e

Cette expression est trouvée dans les chaînes suivantes : 'bateau', 'batteur' et 'joan baez'.

Dans nos exemples, le signe * n'agit que sur la lettre qui le précède directement, pas sur les autres lettres qui figurent avant ou après.

Il existe d'autres signes permettant de contrôler le nombre d'occurences d'une lettre. Je vous ai fait un petit récapitulatif dans le tableau suivant, en prenant des exemples d'expressions avec les lettres a, b et c :

Signe

Explication

Expression

Chaînes contenant l'expression

*

0, 1 ou plus

abc*

'ab', 'abc', 'abcc', 'abcccccc'

+

1 ou plus

abc+

'abc', 'abcc', 'abccc'

?

0 ou 1

abc?

'ab', 'abc'

Vous pouvez également contrôler précisément le nombre d'occurences grâce aux accolades :

  • E{4} : signifie 4 fois la lettre E majuscule ;

  • E{2,4} : signifie de 2 à 4 fois la lettre E majuscule ;

  • E{,5} : signifie de 0 à 5 fois la lettre E majuscule ;

  • E{8,} : signifie 8 fois minimum la lettre E majuscule.

Les classes de caractères

Vous pouvez préciser entre crochets plusieurs caractères ou classes de caractères. Par exemple, si vous écrivez [abcd], cela signifie : l'une des lettres parmi a, b, c et d.

Pour exprimer des classes, vous pouvez utiliser le tiret - entre deux lettres. Par exemple, l'expression [A-Z] signifie « une lettre majuscule ». Vous pouvez préciser plusieurs classes ou possibilités dans votre expression. Ainsi, l'expression [A-Za-z0-9] signifie « une lettre, majuscule ou minuscule, ou un chiffre ».

Vous pouvez aussi contrôler l'occurence des classes comme nous l'avons vu juste au-dessus. Si vous voulez par exemple rechercher 5 lettres majuscules qui se suivent dans une chaîne, votre expression sera [A-Z]{5}.

Les groupes

Je vous donne beaucoup de choses à retenir et vous n'avez pas encore l'occasion de pratiquer. C'est le dernier point sur lequel je vais m'attarder et il sera rapide : comme je l'ai dit plus haut, si vous voulez par exemple contrôler le nombre d'occurences d'un caractère, vous ajoutez derrière un signe particulier (un astérisque, un point d'interrogation, des accolades…). Mais si vous voulez appliquer ce contrôle d'occurence à plusieurs caractères, vous allez placer ces caractères entre parenthèses.

(cha){2,5}

Cette expression sera vérifiée pour les chaînes contenant la séquence 'cha' répétée entre deux et cinq fois. Les séquences 'cha' doivent se suivre naturellement.

Les groupes sont également utiles pour remplacer des portions de notre chaîne mais nous y reviendront plus tard, quand sera venue l'heure de la pratique… Quoi ? C'est l'heure ? Ah bah c'est parti, alors !

Le module re

Le module re a été spécialement conçu pour travailler avec les expressions régulières (Regular Expressions). Il définit plusieurs fonctions utiles, que nous allons découvrir, ainsi que des objets propres pour modéliser des expressions.

Chercher dans une chaîne

Nous allons pour ce faire utiliser la fonction search du module re. Bien entendu, pour pouvoir l'utiliser, il faut l'importer.

>>> import re
>>>

La fonction search attend deux paramètres obligatoires : l'expression régulière, sous la forme d'une chaîne, et la chaîne de caractères dans laquelle on recherche cette expression. Si l'expression est trouvée, la fonction renvoie un objet symbolisant l'expression recherchée. Sinon, elle renvoie None.

Pour symboliser les caractères spéciaux dans les expressions régulières, il est nécessaire d'échapper l'anti-slash en le faisant précéder d'un autre anti-slash. Cela veut dire que pour écrire le caractère spécial \w, vous allez devoir écrire \\w.

C'est assez peu pratique et parfois gênant pour la lisibilité. C'est pourquoi je vous conseille d'utiliser un format de chaîne que nous n'avons pas vu jusqu'à présent : en plaçant un r avant le délimiteur qui ouvre notre chaîne, tous les caractères anti-slash qu'elle contient sont échappés.

>>> r'\n'
'\\n'
>>>

Si vous avez du mal à voir l'intérêt, je vous conseille simplement de vous rappeler de mettre un r avant d'écrire des chaînes contenant des expressions, comme vous allez le voir dans les exemples que je vais vous donner.

Mais revenons à notre fonction search. Nous allons mettre en pratique ce que nous avons vu précédemment :

>>> re.search(r"abc", "abcdef")
<_sre.SRE_Match object at 0x00AC1640>
>>> re.search(r"abc", "abacadaeaf")
>>> re.search(r"abc*", "ab")
<_sre.SRE_Match object at 0x00AC1800>
>>> re.search(r"abc*", "abccc")
<_sre.SRE_Match object at 0x00AC1640>
>>> re.search(r"chat*", "chateau")
<_sre.SRE_Match object at 0x00AC1800>
>>>

Comme vous le voyez, si l'expression est trouvée dans la chaîne, un objet de la classe _sre.SRE_Match est renvoyé. Si l'expression n'est pas trouvée, la fonction renvoie None.

Cela fait qu'il est extrêmement facile de savoir si une expression est contenue dans une chaîne :

if re.match(expression, chaine) is not None:
    # Si l'expression est dans la chaîne
    # Ou alors, plus intuitivement
if re.match(expression, chaine):

N'hésitez pas à tester des syntaxes plus complexes et plus utiles. Tenez, par exemple, comment obliger l'utilisateur à saisir un numéro de téléphone ?

Avec le bref descriptif que je vous ai donné dans ce chapitre, vous pouvez théoriquement y arriver. Mais c'est quand même une regex assez complexe alors je vous la donne : prenez le temps de la décortiquer si vous le souhaitez.

Notre regex doit vérifier qu'une chaîne est un numéro de téléphone. L'utilisateur peut saisir un numéro de différentes façons :

  • 0X XX XX XX XX

  • 0X-XX-XX-XX-XX

  • 0X.XX.XX.XX.XX

  • 0XXXXXXXXX

Autrement dit :

  • le premier chiffre doit être un 0 ;

  • le second chiffre, ainsi que tous ceux qui suivent (9 en tout, sans compter le 0 d'origine) doivent être compris entre 0 et 9 ;

  • tous les deux chiffres, on peut avoir un délimiteur optionnel (un tiret, un point ou un espace).

Voici la regex que je vous propose :

^0[0-9]([ .-]?[0-9]{2}){4}$

ARGH ! C'est illisible ton truc !

Je reconnais que c'est assez peu clair. Décomposons la formule :

  • D'abord, on trouve un caractère accent circonflexe ^ qui veut dire qu'on cherche l'expression au début de la chaîne. Vous pouvez aussi voir, à la fin de la regex, le symbole $ qui veut dire que l'expression doit être à la fin de la chaîne. Si l'expression doit être au début et à la fin de la chaîne, cela signifie que la chaîne dans laquelle on recherche ne doit rien contenir d'autre que l'expression.

  • Nous avons ensuite le 0 qui veut simplement dire que le premier caractère de notre chaîne doit être un 0.

  • Nous avons ensuite une classe de caractère [0-9]. Cela signifie qu'après le 0, on doit trouver un chiffre compris entre 0 et 9 (peut-être 0, peut-être 1, peut-être 2…).

  • Ensuite, cela se complique. Vous avez une parenthèse qui matérialise le début d'un groupe. Dans ce groupe, nous trouvons, dans l'ordre :

    • D'abord une classe [ .-] qui veut dire « soit un espace, soit un point, soit un tiret ». Juste après cette classe, vous avez un signe ? qui signifie que cette classe est optionnelle.

    • Après la définition de notre délimiteur, nous trouvons une classe [0-9] qui signifie encore une fois « un chiffre entre 0 et 9 ». Après cette classe, entre accolades, vous pouvez voir le nombre de chiffres attendus (2).

  • Ce groupe, contenant un séparateur optionnel et deux chiffres, doit se retrouver quatre fois dans notre expression (après la parenthèse fermante, vous trouvez entre accolades le contrôle du nombre d'occurences).

Si vous regardez bien nos numéros de téléphone, vous vous rendez compte que notre regex s'applique aux différents cas présentés. La définition de notre numéro de téléphone n'est pas vraie pour tous les numéros. Cette regex est un exemple et même une base pour vous permettre de saisir le concept.

Si vous voulez que l'utilisateur saisisse un numéro de téléphone, voici le code auquel vous pourriez arriver :

import re
chaine = ""
expression = r"^0[0-9]([ .-]?[0-9]{2}){4}$"
while re.search(expression, chaine) is None:
    chaine = input("Saisissez un numéro de téléphone (valide) :")

Remplacer une expression

Le remplacement est un peu plus complexe. Je ne vais pas vous montrer d'exemples réellement utiles car ils s'appuient en général sur des expressions assez difficiles à comprendre.

Pour remplacer une partie d'une chaîne de caractères sur la base d'une regex, nous allons utiliser la fonction sub du module re.

Elle prend trois paramètres :

  • l'expression à rechercher ;

  • par quoi remplacer cette expression ;

  • la chaîne d'origine.

Elle renvoie la chaîne modifiée.

Des groupes numérotés

Pour remplacer une partie de l'expression, on doit d'abord utiliser des groupes. Si vous vous rappelez, les groupes sont indiqués entre parenthèses.

(a)b(cd)

Dans cet exemple, (a) est le premier groupe et (cd) est le second.

L'ordre des groupes est important dans cet exemple. Dans notre expression de remplacement, nous pouvons appeler nos groupes grâce à \<numéro du groupe>. Pour une fois, on compte à partir de 1.

Ce n'est pas très clair ? Regardez cet exemple simple :

>>> re.sub(r"(ab)", r" \1 ", "abcdef")
' ab cdef'
>>>

On se contente ici de remplacer 'ab' par ' ab '.

Je vous l'accorde, on serait parvenu au même résultat en utilisant la méthode replace de notre chaîne. Mais les expressions régulières sont bien plus précises que cela : vous commencez à vous en rendre compte, je pense.

Je vous laisse le soin de creuser la question, je préfère ne pas vous présenter tout de suite des expressions trop complexes.

Donner des noms à nos groupes

Nous pouvons également donner des noms à nos groupes. Cela peut être plus clair que de compter sur des numéros. Pour cela, il faut faire suivre la parenthèse ouvrant le groupe d'un point d'interrogation, d'un P majuscule et du nom du groupe entre chevrons <>.

(?P<id>[0-9]{2})

Dans l'expression de remplacement, on utilisera l'expression \g<nom du groupe> pour symboliser le groupe. Prenons un exemple :

>>> texte = """
... nom='Task1', id=8
... nom='Task2', id=31
... nom='Task3', id=127"""
... ...
... 
>>> print(re.sub(r"id=(?P<id>[0-9]+)", r"id[\g<id>]", texte))
nom='Task1', id[8]
nom='Task2', id[31]
nom='Task3', id[127]
...
>>>

Des expressions compilées

Si, dans votre programme, vous utilisez plusieurs fois les mêmes expressions régulières, il peut être utile de les compiler. Le module re propose en effet de conserver votre expression régulière sous la forme d'un objet que vous pouvez stocker dans votre programme. Si vous devez chercher cette expression dans une chaîne, vous passez par des méthodes de l'expression. Cela vous fait gagner en performances si vous faites souvent appel à cette expression.

Par exemple, j'ai une expression qui est appelée quand l'utilisateur saisit son mot de passe. Je veux vérifier que son mot de passe fait bien six caractères au minimum et qu'il ne contient que des lettres majuscules, minuscules et des chiffres. Voici l'expression à laquelle j'arrive :

^[A-Za-z0-9]{6,}$

À chaque fois qu'un utilisateur saisit un mot de passe, le programme va appeler re.search pour vérifier que celui-ci respecte bien les critères de l'expression. Il serait plus judicieux de conserver l'expression en mémoire.

On utilise pour ce faire la méthode compile du module re. On stocke la valeur renvoyée (une expression régulière compilée) dans une variable, c'est un objet standard pour le reste.

chn_mdp = r"^[A-Za-z0-9]{6,}$"
exp_mdp = re.compile(chn_mdp)

Ensuite, vous pouvez utiliser directement cette expression compilée. Elle possède plusieurs méthodes utiles, dont search et sub que nous avons vu plus haut. À la différence des fonctions du module re portant les mêmes noms, elles ne prennent pas en premier paramètre l'expression (celle-ci se trouve directement dans l'objet).

Voyez plutôt :

chn_mdp = r"^[A-Za-z0-9]{6,}$"
exp_mdp = re.compile(chn_mdp)
mot_de_passe = ""
while exp_mdp.search(mot_de_passe) is None:
    mot_de_passe = input("Tapez votre mot de passe : ")

En résumé

  • Les expressions régulières permettent de chercher et remplacer certaines expressions dans des chaînes de caractères.

  • Le module re de Python permet de manipuler des expressions régulières en Python.

  • La fonction search du module re permet de chercher une expression dans une chaîne.

  • Pour remplacer une certaine expression dans une chaîne, on utilise la fonction sub du module re.

  • On peut également compiler les expressions régulières grâce à la fonction compile du module re.

Découvrez aussi ce cours en...

Exemple de certificat de réussite
Exemple de certificat de réussite