2.36. Groupes et ancres

Un motif peut faire plus qu’indiquer « cette chaîne correspond » – il peut séparer les fragments correspondants et transmettre chacun d’eux à l’application par son nom. Des parenthèses autour d’une partie d’un motif en font un groupe de capture ; l’objet de correspondance expose alors chaque groupe sous forme de sous-chaîne distincte.

2.36.1. Groupes de capture

Entourez n’importe quelle partie d’un motif de (...) pour capturer ce qu’elle a fait correspondre

>>> import re
>>> m = re.search(r'temp (\d+) at (\d+)s', 'temp 42 at 137s ok')
>>> m.group(0)
'temp 42 at 137s'
>>> m.group(1)
'42'
>>> m.group(2)
'137'
  • Le groupe 0 correspond toujours à la correspondance entière.

  • Les groupes 1, 2, … sont les sous-chaînes capturées, numérotées de gauche à droite selon leur parenthèse ouvrante.

  • Appeler match.group() avec un indice au-delà du dernier groupe lève une IndexError.

Un motif courant consiste à « faire correspondre une structure connue et capturer les parties variables sous forme d’entiers »

def parse_temp(line):
    m = re.search(r'temp (\d+) at (\d+)s', line)
    if not m:
        return None
    return int(m.group(1)), int(m.group(2))

2.36.2. Groupes sans capture

Les parenthèses regroupent aussi une sous-expression afin qu’un quantificateur puisse s’appliquer à l’ensemble du groupe. C’est le seul but du regroupement dans r'(ab)+' – « un ou plusieurs ab ». Le fait que ab apparaisse en tant que groupe 1 est un effet secondaire.

Pour regrouper sans capturer, utilisez (?:...)

>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'

Les groupes sans capture gardent les numéros de groupe ordonnés lorsqu’un motif utilise le regroupement pour la structure mais ne se soucie pas d’extraire chaque fragment.

2.36.3. Ancres

Les ancres ne font pas correspondre un caractère – elles font correspondre une position.

  • ^ – début de la chaîne.

  • $ – fin de la chaîne.

Les ancres sont ce qui fait que re.match() et re.search() se comportent différemment. re.match(p, s) est identique à re.search('^' + p, s) : cela force le motif à commencer à la position 0. Ajouter $ à la fin d’un motif fait alors correspondre le motif à la chaîne entière et à rien d’autre

>>> re.search(r'^\d+$', '12345')
<match num=1>
>>> re.search(r'^\d+$', '12345 ok') is None
True

^ et $ dans le module re de MicroPython désignent toujours le début et la fin de la chaîne entière passée à re.search(). Il n’existe pas d’indicateur re.MULTILINE pour les faire correspondre à chaque saut de ligne interne, et $ ne correspond pas non plus à la position précédant un \n final – il doit s’agir de la toute fin de l’entrée. Pour obtenir un comportement ligne par ligne, découpez d’abord l’entrée aux sauts de ligne et exécutez le motif sur chaque ligne.

2.36.4. Ensembles de caractères

Les crochets définissent un ensemble explicite de caractères. La correspondance consomme exactement un caractère de l’ensemble.

  • [abc] – l’un de a, b, c.

  • [a-z] – un caractère dans la plage a-z (inclusive).

  • [a-zA-Z0-9] – lettres ou chiffres. Trois plages combinées.

  • [^abc]pas l’un de a, b, c. Le ^ ne nie que lorsqu’il est le premier caractère à l’intérieur des crochets.

Exemples

>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest').group(0)
'1A2B3C'
>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest') is None
True

Le premier appel renvoie en pratique None car le texte littéral est en minuscules. Le module re de MicroPython n’a pas d’indicateur re.IGNORECASE – pour une correspondance insensible à la casse, écrivez les deux casses dans l’ensemble

>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'

Les raccourcis de classe (\d, \s, \w, et leurs formes niées) peuvent aussi être utilisés à l’intérieur de [...] : [\w-] signifie « caractères de mot ou un tiret littéral ».

2.36.5. Quantificateurs gloutons et paresseux

Les quantificateurs *, +, ?, et {m,n} sont gloutons par défaut – ils font correspondre autant de caractères que le reste du motif le permet encore. Souvent, c’est exactement ce que l’on veut ; parfois non

>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'

Le .+ glouton a tout englouti jusqu’au dernier >. Ajouter ? rend le quantificateur paresseux – il fait correspondre le moins possible

>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'

La forme paresseuse s’arrête au premier >. Les quantificateurs paresseux apparaissent constamment lors de l’extraction de délimiteurs appariés d’une chaîne.

2.36.6. Références arrière dans la substitution

re.sub() peut renvoyer aux groupes capturés dans la chaîne de remplacement via \1, \2, … La substitution réécrit chaque correspondance à partir des fragments capturés

>>> re.sub(r'(\d+)\.(\d+)', r'\2.\1', 'swap 12.34 and 5.6')
'swap 34.12 and 6.5'

Chaque correspondance capture deux nombres, et le remplacement les permute. \g<1> est une syntaxe alternative pour la même chose – utile lorsque le caractère suivant dans le remplacement est un chiffre (r'\g<1>0' pour ajouter un zéro littéral au groupe 1 plutôt que de lire « groupe 10 »).

2.36.7. Ce qui n’est pas disponible

Un rappel de ce que le module re de MicroPython ne prend pas en charge, au cas où un motif de CPython atterrirait ici et vous surprendrait :

  • Les assertions avant (?=...) et arrière (?<=...) – non implémentées.

  • Les groupes nommés (?P<name>...) et les références arrière nommées (?P=name) – non implémentés.

  • Les constantes d’indicateur comme re.IGNORECASE, re.MULTILINE, re.DOTALL – non prises en compte. Construisez vous-même l’ensemble insensible à la casse ou pré-découpez l’entrée.

  • Les méthodes match.groups(), match.span(), match.start(), et match.end() sont conditionnées à un niveau de ROM qu’aucune carte OpenMV livrée n’active. Le code qui en dépend ne fonctionnera pas sur la caméra.

Avec les motifs, les groupes et les ancres, la boîte à outils regex de la caméra est suffisamment réduite pour être apprise en une seule séance et suffisamment riche pour tout faire, à l’exception de l’analyse sensible au contexte.