2.35. Bases des motifs

Une expression régulière est un petit langage permettant de décrire des chaînes par leur forme plutôt que par leur contenu exact. Utilisez-la lorsqu’une chaîne correspond si et seulement si elle suit un motif que vous pouvez décrire mais pas énumérer – « une suite de chiffres suivie d’une unité », « une ligne qui commence par ERROR et se termine par un nombre », « n’importe laquelle de ces extensions de fichier, dans n’importe quel ordre, avec des préfixes v optionnels ».

Ne recourez à re que lorsqu’une simple méthode de chaîne ne suffit pas.

Chacune de ces options est plus rapide, plus facile à lire et moins source d’erreurs que la regex équivalente. Utilisez les regex lorsque la forme de la chaîne importe et que la sous-chaîne exacte n’importe pas.

2.35.1. Les quatre choses que vous utiliserez

Le module re de MicroPython expose quatre choses :

  • re.compile() – transformer une chaîne de motif en un objet motif compilé que vous pouvez réutiliser.

  • re.match() – essayer le motif au début d’une chaîne. Le motif est ancré à la position 0.

  • re.search() – essayer le motif n’importe où dans une chaîne. Renvoie la première correspondance.

  • re.sub() – trouver chaque correspondance et la remplacer.

Omissions notables par rapport à CPython : pas de re.findall, pas de re.finditer, pas de re.split au niveau du module (les motifs compilés disposent d’une méthode split à la place), pas de re.fullmatch, pas de constantes d’indicateur comme re.IGNORECASE. Là où vous recourriez à l’une d’elles sur CPython, construisez l’équivalent à partir de re.search() dans une boucle.

2.35.2. Un premier motif

Le motif r'\d+' fait correspondre un ou plusieurs chiffres

>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'

Quelques points à remarquer :

  • Le motif est écrit sous forme de chaîne brute (r'...') afin que la barre oblique inverse dans \d atteigne re au lieu d’être traitée comme une séquence d’échappement de chaîne Python. Utilisez toujours des chaînes brutes pour les motifs regex.

  • re.search() renvoie un objet de correspondance en cas de succès et None en cas d’échec. Vérifiez toujours avant d’appeler match.group().

  • m.group(0) est le texte complet que le motif a fait correspondre. Les groupes 1, 2, … apparaissent plus tard, une fois que le motif contient des parenthèses de capture.

Le même motif avec re.match() renvoie None car la chaîne ne commence pas par un chiffre

>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>

2.35.3. Les éléments d’un motif

La plupart des motifs utiles sont construits à partir d’un petit ensemble d’éléments. Ceux qui fonctionnent dans MicroPython :

Caractères littéraux – tout caractère non spécial se fait correspondre lui-même. hello correspond à hello.

Caractères spéciaux. ^ $ * + ? { } [ ] \ | ( ) ont tous des significations décrites ci-dessous. Pour faire correspondre l’un d’eux littéralement, échappez-le avec une barre oblique inverse : \. correspond à un point littéral.

Classes de caractères – des raccourcis pour des ensembles de caractères courants :

  • \d – tout chiffre 0-9

  • \D – tout caractère non numérique

  • \s – tout caractère d’espacement (espace, tabulation, saut de ligne)

  • \S – tout caractère non blanc

  • \w – tout caractère « de mot » : lettres, chiffres, trait de soulignement

  • \W – tout caractère qui n’est pas un caractère de mot

  • . – tout caractère sauf le saut de ligne

Quantificateurs – combien de fois l’élément précédent doit correspondre :

  • * – zéro ou plus (glouton)

  • + – un ou plus (glouton)

  • ? – zéro ou un

  • {n} – exactement n

  • {m,n} – entre m et n (inclus)

Combinaison : \d{3}-\d{4} fait correspondre trois chiffres, un tiret, quatre chiffres. \s+ fait correspondre un ou plusieurs caractères d’espacement. hello.*world fait correspondre hello, n’importe quoi (y compris rien), puis world.

Note

Glouton signifie que le quantificateur consomme autant d’entrée que possible tout en laissant le reste du motif correspondre. Face à hello x world y world, le .* dans hello.*world fait correspondre la plus longue suite qui laisse encore un world à la fin – il capture x world y, et non le plus court x. Il en va de même pour + et la forme de plage {m,n} : le moteur prend la correspondance la plus longue possible, puis ne recule que si le reste du motif échoue.

2.35.4. Substitution

re.sub() trouve chaque correspondance et la remplace par une chaîne. Le remplacement peut référencer les groupes capturés via \1, \2, … (abordé plus loin avec le reste de la syntaxe des groupes). Sans groupes, re.sub est une simple recherche-remplacement sur une regex

>>> re.sub(r'\s+', ' ', 'too    many   spaces')
'too many spaces'

>>> re.sub(r'\d+', 'N', 'log 12, log 345, log 6')
'log N, log N, log N'

Le troisième argument est la chaîne sur laquelle opérer ; le résultat est une nouvelle chaîne où chaque correspondance est remplacée.

2.35.5. Découpage – sur un motif compilé uniquement

Il n’y a pas de re.split au niveau du module. Pour découper sur une regex, compilez d’abord le motif et appelez sa méthode split

>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c ,  d')
['a', 'b', 'c', 'd']

Le deuxième argument optionnel limite le nombre de découpages

>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']

2.35.6. Compiler pour réutiliser

Si le même motif s’exécute de nombreuses fois – à l’intérieur d’une boucle ou dans une fonction très sollicitée – compilez-le une fois et réutilisez l’objet compilé

digit_run = re.compile(r'\d+')

def first_number(line):
    m = digit_run.search(line)
    return int(m.group(0)) if m else None

Appeler pattern.match() et pattern.search() sur un objet compilé revient au même que les fonctions au niveau du module mais évite le coût de recompilation à chaque appel.

2.35.7. Motifs qui ne correspondent à rien

Trois motifs en particulier piègent les développeurs :

  • .* correspond à la chaîne vide. re.search(r'.*', s).group(0) renvoie '' sur n’importe quelle entrée.

  • Un motif comportant un caractère spécial non échappé est une erreur de syntaxe. re.compile(r'cost: $5') lève une ValueError car $ signifie « fin de chaîne ». Utilisez r'cost: \$5'.

  • Le point . ne correspond pas à un saut de ligne. Pour faire correspondre à travers les sauts de ligne, écrivez le motif pour les gérer explicitement avec [\s\S] ou alimentez une ligne à la fois.

Avec ces éléments, un motif peut faire correspondre presque n’importe quelle portion de texte de forme fixe. Extraire des données structurées de la correspondance nécessite des groupes de capture.