dernière modification : 2023

Les regex en Python

1. Raw string

Les string Python interprêtent certaines séquences (comme \n, \r, \t, \\, etc.) de façon particulière. Donc par exemple, pour représenter littéralement la chaîne "tangea\n\te" :

s1 = "tangea\n\te"

n’est pas la solution, puisque print(s1) donne :

output
tangea
      e

Il faut écrire :

s1 = "tangea\\n\\te"

raw string est un type chaîne de caractères dans lequel il n’y a aucune interprétation :

s2 = r"tangea\n\te"   (1)
print(s2)
print(s1==s2)               # True
1 le r qui signifie rawstring

donne :

output
tangeante
True

1.1. Raw string et regex

Dans une regex, le "\" a aussi une signification particulière. Mémoriser une regex dans une string nécessite de prendre en compte ce double niveau d’interprétation.

Windows utilise le caractère "\" comme séparateur dans les noms de fichiers. Par exemple, le homedir d’un utilisateur toto ressemble à c:\users\toto\documents\.

Pour capturer l’utilisateur, on a envie d’écrire une Perl regex comme :

^c:\users\(.*?)\documents\$

mais comme dans une regex, le "\" possède une signification particulière, la rexp correcte est :

^c:\\users\\(.*?)\\document\\$

Mémoriser cette regex dans une string Python donne donc :

s = "^c:\\\\users\\\\(.*?)\\\\document\\\\$"

Pas très limpide ! Utiliser une raw string rend les choses un peu plus lisibles :

s = r"^c:\\users\\(.*?)\\document\\$"

Conclusion

En Python, il est plus simple de représenter une regex littérale avec une raw string :

# représenter la regex '^\..'
regex1 = "^\\.."     # par une string
regex2 = r"^\.."     # par une raw string

2. Le module re

Le module re offre des fonctions de manipulation de regex, principalement : match(), search(), findall(), sub() et split().

Les regex connues de Python sont un sous-ensemble des Perl regex (autrement dit, Python gère toutes les regex étendues, plus certaines Perl regex).

Les regex pouvant apparaître abscons pour un néophyte, lorsque les opérations à réaliser sont simples, les méthodes de str (comme replace, translate, index, find, split, count) peuvent s’avérer suffisantes.

2.1. Rechercher une correspondance

match() détermine si une chaine commence par un motif donné, et search() détermine s’il est présent.

la fonction match() est donc particulièrement mal nommée, puisqu’elle ne réalise pas ce qu’il est communément mis derrière matching a regex

Elles s’utilisent soit comme des fonctions de module :

import re

s = "Le navire roulait sous un ciel sans nuages"

print(re.match(r" un ",s))          # None
print(bool(re.match(r" un ",s)))    # False
print(re.search(r" un ",s))         # <_sre.SRE_Match object at 0x7f752c57d648>
print(bool(re.search(r" un ",s)))   # True

soit comme des méthodes de l’objet créé par re.compile() :

motif = re.compile(r" un ")
print(type(motif))                  # <class '_sre.SRE_Pattern'>

print(bool(motif.match(s)))         # False
print(bool(motif.search(s)))        # True

En cas de succès, match() et search() renvoient un objet contenant la partie qui s’identifie au motif :

motif = re.compile(r"c.*l")

found=motif.match(s)
print(found)   # None

found=motif.search(s)
print(found)   # <_sre.SRE_Match object; span=(26, 30), match='ciel'>

Cet objet dispose des méthodes :

  • group() qui renvoie la portion qui s’identifie au motif,

  • span() qui renvoie un tuple (position de début, position de fin)

print(found.group())  # ciel
print(found.span())   # (26, 30)

2.2. Rechercher toutes les occurrences d’un motif

findall() renvoie une liste contenant toutes les sous-chaines s’identifiant à un motif. La version itérateur équivalente, finditer(), renvoie un itérateur parcourant toutes les sous-chaines s’identifiant à un motif

s = "Le navire roulait sous un ciel sans nuages"

found = re.findall(r"l.",s)
print(type(found))            # <class 'list'>
print(found)                  # ['la', 'l ']

2.3. Compiler une regex

motif = re.compile(r" un ")   # compilation
found = motif.match(s)        # confrontation

est donc globalement équivalent à :

found = re.match(r" un ",s)   # compilation + confrontation

mais il est plus performat d’écrire :

motif = re.compile(r"^(.)(.).\2\1$")
for line in liste:
  if motif.match(line):
    print(line)

qui compile une fois pour toute la regex, que d’écrire :

for line in liste:
  if re.match(r"^(.)(.).\2\1$",line):
    print(line)

qui compile la regex à chaque tour de boucle.

Autre avantage, des options peuvent être spécifiées à compile() pour modifier le comportement des confrontations ultérieures. Par exemple :

motif = re.compile(r"l.?e",re.IGNORECASE)
found=motif.findall(s)
print(found)           # ['Le', 'la', 'l ']

2.4. Capturer

On capture avec des () dans la regex et on récupère la capture avec les méthodes group() et groups(). Exemple :

import re

s = "Le navire roulait sous un ciel sans nuages"

capt = re.search(r" (.*?) .*n (.*?) ",s)
print(bool(capt))        # True
print(capt)              # <re.Match object; span=(2, 31), match=' navire roulait sous un ciel '>
print(capt.groups())     # ('navire', 'ciel')
print(capt.group(1))     # navire
print(capt.group(2))     # ciel

2.5. Substituer

C’est le rôle de la méthode sub. La regex identifie la portion à remplacer, et sub spécifie par quoi il faut la remplacer. Exemple :

import re

s = "Le navire roulait sous un ciel sans nuages"

capt = re.sub(r"\b(..)\b","[mot de 2 lettres]",s)
print(capt) # [mot de 2 lettres] navire roulait sous [mot de 2 lettres] ciel sans nuages

La regex peut capturer des portions, qui peuvent être réutilisées dans la substitution avec \1, \2, etc. Exemple :

motif = re.compile(r'^\s*(.*)\s*$')
line = "    12 300    "
print(motif.sub(r'\1',line))   # -> "12 300"

2.6. Eclater

C’est le rôle de la méthode split. La regex spécifie les séparateurs, et split éclate la chaine suivant ces séparateurs. Exemple :

sep = re.compile(r"\D+") # suite de caractères non numériques
line = "1 + (2*3) = 7"
print(sep.split(line))   # ['1', '2', '3', '7']

3. Conclusion

L’utilisation des regex en Python passent par des fonctions, ce qui est assez lourd : Python n’est pas le langage le plus souple pour les manier. En proposant des opérateurs spécifiques pour cela, les langages Perl et Ruby offrent une syntaxe plus légère et une meilleure expressivité.