Programmation Python/Regex
En informatique, une expression régulière ou expression rationnelle ou expression normale ou motif, est une chaîne de caractères, qui décrit, selon une syntaxe précise, un ensemble de chaînes de caractères possibles. Les expressions régulières sont également appelées regex (de l'anglais regular expression). Elles sont issues des théories mathématiques des langages formels. Les expressions régulières sont aujourd’hui utilisées pour la lecture, le contrôle, la modification, et l'analyse de textes ainsi que la manipulation des langues formelles que sont les langages informatiques.
L'exemple d'expression régulière suivant permet de valider qu'une chaîne de caractère correspond à la syntaxe d'un nombre entier non signé, c'est à dire une suite non vide de chiffres :
[0-9]+
En détails :
- Les crochets spécifient l'ensemble des caractères auquel doit appartenir le caractère courant de la chaîne. Dans cet exemple, l'ensemble est celui des chiffres de 0 à 9 inclus.
- Le caractère plus indique de répéter le motif précédent au moins une fois (suite non vide).
Les expressions régulières en Python nécessitent d'importer le module natif re[1], ou bien l'installation du module externe regex[2] si besoin des regex Unicode tels que \X
.
import re
chaine = "12345"
if re.compile('[0-9]+').match(chaine):
print "Entier positif"
Syntaxe
modifierLes expressions rationnelles peuvent être analysées et testées via un débogueur en ligne comme https://regex101.com/.
Caractère | Type | Explication |
---|---|---|
.
|
Point | N'importe quel caractère |
[...]
|
crochets | classe de caractères : tous les caractères énumérés dans la classe, avec possibilité de plages dont les bornes sont séparées par "-". Ex : [0-9a-z] pour tout l'alphanumérique en minuscule, ou [0-Z] pour tous les caractères de la table Unicode entre "0" et "Z", c'est-à-dire l'alphanumérique majuscule plus ":;<=>?@"[3].
|
[^...]
|
crochets et circonflexe | classe complémentée : tous les caractères sauf ceux énumérés. |
^
|
circonflexe | Marque le début de la chaîne ou de la ligne. |
$
|
dollar | Marque la fin de la chaîne ou de la ligne. |
|
|
barre verticale | Alternative - ou reconnaît l'un ou l'autre |
(...)
|
parenthèses | groupe de capture : utilisé pour limiter la portée d'un masque ou de l'alternative, grouper un motif répété ou capturer une séquence |
\n
|
référence | Même séquence que celle capturée précédemment par le nème groupe de capture |
\g{n}
|
référence | Même séquence que celle capturée précédemment par le nème groupe de capture |
(?P<nom>pattern)
|
Sous-motif nommé | Nomme le résultat d'un groupe de capture par un nom. |
\g{nom}
|
référence | Même séquence que celle capturée précédemment par le groupe de capture nommé nom. |
Par défaut, les caractères et groupes ne sont pas répétés. Les quantificateurs permettent de spécifier le nombre de répétitions et sont spécifiés immédiatement après le caractère ou groupe concerné.
Caractère | Type | Explication |
---|---|---|
*
|
astérisque | 0, 1 ou plusieurs occurrences |
+
|
plus | 1 ou plusieurs occurrences |
?
|
interrogation | 0 ou 1 occurrence |
{...}
|
accolades | nombre de répétitions : spécifie le nombre de répétitions du motif précédent (minimum et maximum). Avec la présence de la virgule, quand le minimum est absent la valeur par défaut est zéro, quand le maximum est absent la valeur pas défaut est l'infini. Sans virgule (un seul nombre) il s'agit du nombre exact (minimum et maximum ont la même valeur). Exemples :
|
Par défaut les quantificateurs ne recherchent pas forcément la plus longue séquence de répétition possible. Il est possible de les suffixer avec un caractère pour modifier leur comportement.
Caractère | Type | Explication |
---|---|---|
?
|
réticent | Le quantificateur qui précède recherchera la plus petite séquence possible. |
+
|
possessif | Le quantificateur qui précède recherchera la plus grande séquence possible. |
Remarques :
- Les caractères de début et fin de chaîne (
^
et$
) ne fonctionnent pas dans[]
où ils ont un autre rôle. - Les opérateurs
*
et+
sont toujours avides, pour qu'ils laissent la priorité il faut leur apposer un?
à leur suite[4].
Classe | Signification |
---|---|
[[:alpha:]]
|
n'importe quelle lettre |
[[:digit:]]
|
n'importe quel chiffre |
[[:xdigit:]]
|
caractères hexadécimaux |
[[:alnum:]]
|
n'importe quelle lettre ou chiffre |
[[:space:]]
|
n'importe quel espace blanc |
[[:punct:]]
|
n'importe quel signe de ponctuation |
[[:lower:]]
|
n'importe quelle lettre en minuscule |
[[:upper:]]
|
n'importe quelle lettre capitale |
[[:blank:]]
|
espace ou tabulation |
[[:graph:]]
|
caractères affichables et imprimables |
[[:cntrl:]]
|
caractères d'échappement |
[[:print:]]
|
caractères imprimables exceptés ceux de contrôle |
Expression | Signification |
---|---|
\\ |
Antislash |
\C |
Caractère spécial C non interprété : [ ] { } ( ) ? * . : \ & - ^ $
|
\Q...\E |
Séquence littérale non interprétée |
\0xxx |
Caractère Unicode (1 à 3 chiffres octaux) |
\a |
Alarme (ASCII 07) |
\A |
Début de chaîne |
\b |
Caractère de début ou fin de mot |
\B |
Caractère qui n'est pas début ou fin de mot |
\cX |
Caractère de contrôle ASCII (X étant une lettre) |
\d |
Chiffre |
\D |
Non chiffre |
\e |
Escape (ASCII 1B) |
\f |
Form-feed (ASCII 0C) |
\G |
Fin de la correspondance précédente |
\h |
Espace blanc horizontal [ \t\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]
|
\H |
Non espace blanc horizontal [^\h]
|
\n |
Fin de ligne |
\pL , \p{L} , \p{Letter} |
Lettre (dans tout langage) |
\r |
Retour charriot |
\R |
Retour à la ligne, équivaut à \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]
|
\s |
Caractères espace [ \t\n\x0B\f\r]
|
\S |
Non caractères espace [^\s]
|
\t |
Tabulation |
\v |
Espace blanc vertical [\n\x0B\f\r\x85\u2028\u2029]
|
\V |
Non espace blanc vertical [^\v]
|
\w |
Caractère alphanumérique : lettre, chiffre ou underscore |
\W |
Caractère qui n'est pas lettre, chiffre ou underscore |
\xxx |
Caractère Unicode (2 chiffres hexadécimaux) |
\x{xx...x} |
Caractère Unicode (chiffres hexadécimaux) |
\X |
Caractère Unicode du groupe de graphèmes étendu |
\z |
Fin de chaîne |
Constructeurs spéciaux : Ces fonctions précèdent l'expression à laquelle elles s'appliquent, et le tout doit être placé entre parenthèses.
?:
: groupe non capturant. Ignorer le groupe de capture lors de la numérotation des backreferences. Exemple :((?:sous-chaine_non_renvoyée|autre).*)
.- La présence d'un groupe capturant peut engendrer une allocation mémoire supplémentaire. Si une expression régulière particulièrement complexe provoque une erreur de mémoire, essayez de remplacer les groupes capturant non référencés et inutilisés par des groupes non-capturant en ajoutant
?:
juste après la parenthèse ouvrante, et en décalant les numéros des groupes référencés.
- La présence d'un groupe capturant peut engendrer une allocation mémoire supplémentaire. Si une expression régulière particulièrement complexe provoque une erreur de mémoire, essayez de remplacer les groupes capturant non référencés et inutilisés par des groupes non-capturant en ajoutant
?>
: groupe non capturant indépendant.?<=
: positive lookbehind, vérifier (sans consommer) que ce qui précède correspond au motif spécifié. Exemple :- Chercher une lettre u précédée d'une lettre q :
(?<=q)u
- Chercher une lettre u précédée d'une lettre q :
?<!
: negative lookbehind, vérifier (sans consommer) que ce qui précède ne correspond pas au motif spécifié.?=
: positive lookahead, vérifier (sans consommer) que ce qui suit correspond au motif spécifié.?!
: negative lookahead, vérifier (sans consommer) que ce qui suit ne correspond pas au motif spécifié. Exemples :
Options :
Les options d'interprétation sont en général spécifiées à part. Mais certaines API ne permettent pas de les spécifier. Il est possible d'insérer ces options dans l'expression régulière[9].
(?optionsactivées-optionsdésactivées)
Exemples :
- Chercher un mot composé de voyelles sans tenir compte de la casse :
(?i)[AEIOUY]+
- Chercher un mot composé de voyelles en tenant compte de la casse, ici en majuscules :
(?-i)[AEIOUY]+
Les options s'appliquent à toute l'expression quelle que soit leur position dans l'expression.
(?:ma_chaine)*
: groupe optionnel.\1
: résultat du premier groupe de capture dans les remplacements (\2 correspond au deuxième, etc.).
Recherche
modifiercompile()
renvoieNone
si l'expression rationnelle n'est pas trouvée dans la chaîne.
search()
renvoie la position des chaînes recherchées.
#!/usr/bin/env python
import re
chaine = "Test regex Python pour Wikibooks francophone."
if re.compile('Wikibooks').search(chaine):
print "Position du mot Wikibooks : "
print re.search(u'Wikibooks', chaine).start()
# Affiche "23"
print re.search(u'Wikibooks', chaine).end()
# Affiche "32"
Pour voir le motif compilé : re.compile('Wikibooks').pattern
findall()
trouve toutes les correspondances dans un tableau.
finditer()
trouve toutes les correspondances dans un itérateur.
#!/usr/bin/env python
# Affiche tous les mots qui commencent par "Wiki"
import re
chaine = "Wikilivre regex Python pour Wikibooks francophone."
print (re.findall(r"Wiki\w+", chaine))
# Affiche ['Wikilivre', 'Wikibooks']
Les parenthèses imbriquées permettent d'indiquer des mots facultatifs au sein d'un groupe de capture. Ex :
#!/usr/bin/env python
# Trouve à un mot prêt
import re
chaine = "Wikilivre regex Python pour Wikibooks francophone."
regex = r'(Python pour Wikibooks)'
print re.search(regex, chaine).start() # 16
regex = r'(Python (pour )*Wikibooks)'
print re.search(regex, chaine).start() # 16
regex = r'(Python pour (les )*Wikibooks)'
print re.search(regex, chaine).start() # 16
group()
modifierPour accéder aux résultats des groupes de capture, utiliser group()
en partant de 1 (0 étant le match du motif entier) :
#!/usr/bin/env python
import re
chaine = "Wikilivre regex Python pour Wikibooks francophone."
s = re.search(r'(Wiki[a-z]*).*(Wiki[a-z]*)', chaine)
if s:
print s.group(0)
# Affiche 'Wikilivre regex Python pour Wikibooks'
print s.group(1)
# Affiche 'Wikilivre'
print s.group(2)
# Affiche 'Wikibooks'
Flags
modifierLe comportement de certaines expressions peut être reconfiguré en ajoutant un "flag" en paramètre des méthodes[10].
re.IGNORECASE
modifierIgnore la casse. Ainsi dans l'exemple précédent nous pouvions aussi faire :
s = re.search(r'(wiki[a-z]*).*(wiki[a-z]*)', chaine, re.IGNORECASE)
re.MULTILINE
modifierPar défaut, les caractères "^" et "$" désignent le début et la fin de tout le texte. Or, en mode multiligne, un "^" en début de re.search() considérera le début de chaque ligne, et "$" leurs fins.
Pour partir uniquement du début de la chaîne globale, il faut alors ne plus utiliser "re.search()" mais "re.match()"[11].
re.DOTALL
modifierPar défaut, .*
et .+
s'arrêtent aux retours chariot (\n). Pour qu'ils englobent ces retours à la ligne, il faut appeler re.DOTALL
. Exemple :
if re.search(regex, text, re.MULTILINE| re.DOTALL):
Remplacement
modifier#!/usr/bin/env python
# Remplace tous les espaces par des underscores
import re
chaine = "Test regex Python pour Wikibooks francophone."
chaineTriee = re.sub(r' ', "_", chaine)
print chaineTriee
# Affiche "Test_regex_Python_pour_Wikibooks_francophone."
Pour remplacer certains éléments en conservant ceux placés entre parenthèses, il faut les désigner par \1, \2, \3...
#!/usr/bin/env python
# Ajoute des guillemets à tous les mots suivent "livre"
import re
chaine = "Test regex Python pour le livre Python de Wikibooks francophone."
chaineTriee = re.sub(r'(.*)livre (\w+)(.*)', r'\1livre "\2"\3', chaine)
print chaineTriee
# Affiche "Test regex Python pour le livre "Python" de Wikibooks francophone."
Remarque : si les paramètres (\1, \2...) sont remplacés par le symbole �, vérifier que la chaine regex est bien encodée avec r.
Les différents contenus d'un même groupe de capture sont remplacés par le premier \1. Pour éviter cela, il faut les traiter un par un avec "finditer()".
Dans un contexte multi-ligne, re.sub()
ne recherche pas tout comme re.search()
(qui a un global flag).
Exemple : remplacement de la balise "font color=" par "span style=font-size:".
text = r'<font color=green>Vert</font> / <font color=red>rouge</font>'
regex = r'<font color=([^>]*)>'
pattern = re.compile(regex, re.UNICODE)
for match in pattern.finditer(text):
print u'Remplacement de ' + match.group(0) + u' par <span style="font-color:' + match.group(1) + u'">'
text = text.replace(match.group(0), u'<span style="font-color:' + match.group(1) + u'">')
text = text.replace('</font>', u'</span>')
input(text)
Exemples de formules
modifier- Récupérer le premier modèle 1 wiki non imbriquée dans un autre modèle :
page = u'{{Modèle2|Paramètre2, {{Modèle1|Paramètre3}} }}, {{Modèle1|Paramètre4}}'
regex = r'({{(.*?)}}|.)*[^}]*'
input(re.sub(regex, r'\2', page))
Pour indiquer un nombre précis d'occurrences, utiliser "{nombre}". Ex :
#!/usr/bin/env python
import re
chaine = 'w.1, ww.2, www.3, wwww.4'
print re.sub(r' w{3}\.', ' http://www.', chaine)
w.1, ww.2, http://www.3, wwww.4
Idem pour une plage de nombres : {min,max}.
Quand on injecte une variable dans un motif, il faut échapper ses caractères interprétables avec re.escape()
.
Références
modifier- ↑ https://docs.python.org/3/howto/regex.html
- ↑ https://pypi.org/project/regex/
- ↑ https://unicode-table.com/fr/
- ↑ https://docstore.mik.ua/orelly/webprog/pcook/ch13_05.htm
- ↑ https://www.regular-expressions.info/posixbrackets.html
- ↑ https://www.regular-expressions.info/unicode.html
- ↑ https://www.regextester.com/15
- ↑ Jan Goyvaerts, Steven Levithan, Regular Expressions Cookbook, O'Reilly Media, Inc., (lire en ligne)
- ↑ Les options sont appelées modificateurs (modifiers en anglais), voir https://www.regular-expressions.info/modifiers.html
- ↑ https://docs.python.org/2/library/re.html#module-contents
- ↑ https://docs.python.org/2/library/re.html#search-vs-match