Les expressions régulières sont un langage puissant pour faire correspondre des motifs de texte. Cette page donne une introduction de base aux expressions régulières elles-mêmes suffisantes pour nos exercices Python et montre comment les expressions régulières fonctionnent en Python. Le module Python « re » fournit le support des expressions régulières.
En Python, une recherche par expression régulière est typiquement écrite comme:
match = re.search(pat, str)
La méthode re.search() prend un motif d’expression régulière et une chaîne de caractères et recherche ce motif dans la chaîne. Si la recherche est fructueuse, search() renvoie un objet match ou None sinon. Par conséquent, la recherche est généralement immédiatement suivie d’une instruction if pour tester si la recherche a réussi, comme le montre l’exemple suivant qui recherche le motif ‘mot:’ suivi d’un mot de 3 lettres (détails ci-dessous):
str = 'an example word:cat!!'match = re.search(r'word:\w\w\w', str)# If-statement after search() tests if it succeededif match: print 'found', match.group() ## 'found word:cat'else: print 'did not find'
Le code match = re.search(pat, str)
stocke le résultat de la recherche dans une variable nommée « match ». Ensuite, l’énoncé if teste le match — si vrai, la recherche a réussi et match.group() est le texte correspondant (par exemple, ‘mot:chat’). Sinon, si match est faux (None pour être plus précis), alors la recherche n’a pas réussi et il n’y a pas de texte correspondant.
Le ‘r’ au début de la chaîne de motifs désigne une chaîne python « brute » qui passe par les antislashs sans changement, ce qui est très pratique pour les expressions régulières (Java a grandement besoin de cette fonctionnalité !). Je vous recommande de toujours écrire les chaînes de motifs avec le ‘r’ juste par habitude.
Modèles de base
La puissance des expressions régulières est qu’elles peuvent spécifier des modèles, pas seulement des caractères fixes. Voici les motifs les plus basiques qui correspondent à des caractères uniques :
- a, X, 9, < — les caractères ordinaires se correspondent juste exactement. Les métacaractères qui ne correspondent pas à eux-mêmes parce qu’ils ont des significations spéciales sont : . ^ $ * + ? { \ | ( ) (détails ci-dessous)
- . (un point) — correspond à tout caractère unique, à l’exception de la nouvelle ligne ‘\n’
- \w — (w minuscule) correspond à un caractère « mot » : une lettre, un chiffre ou une barre inférieure . Notez que bien que « mot » soit le mnémonique pour cela, il ne correspond qu’à un seul caractère de mot, pas à un mot entier. \W (W majuscule) correspond à tout caractère non-mot.
- \b — limite entre le mot et le non-mot
- \s — (s minuscule) correspond à un seul caractère d’espacement — espace, nouvelle ligne, retour, tabulation, forme . \S (S majuscule) correspond à tout caractère non espace blanc.
- \t, \n, \r — tabulation, nouvelle ligne, retour
- \d — chiffre décimal (certains anciens utilitaires de regex ne supportent pas mais \d, mais ils supportent tous \w et \s)
- ^ = début, $ = fin — correspond au début ou à la fin de la chaîne de caractères
- \ — inhibe la « spécificité » d’un caractère. Ainsi, par exemple, utilisez \. pour correspondre à un point ou \\\ pour correspondre à une barre oblique. Si vous n’êtes pas sûr qu’un caractère ait une signification spéciale, comme ‘@’, vous pouvez mettre une barre oblique devant, \@, pour vous assurer qu’il est traité juste comme un caractère.
Exemples de base
Joke : comment appelez-vous un cochon avec trois yeux ? piiig !
Les règles de base de la recherche par expression régulière d’un motif dans une chaîne de caractères sont :
- La recherche se déroule dans la chaîne de caractères du début à la fin, s’arrêtant à la première correspondance trouvée
- Tout le motif doit être apparié, mais pas toute la chaîne de caractères
- Si
match = re.search(pat, str)
est réussi, match n’est pas None et en particulier match.group() est le texte correspondant
## Search for pattern 'iii' in string 'piiig'. ## All of the pattern must match, but it may appear anywhere. ## On success, match.group() is matched text. match = re.search(r'iii', 'piiig') # found, match.group() == "iii" match = re.search(r'igs', 'piiig') # not found, match == None ## . = any char but \n match = re.search(r'..g', 'piiig') # found, match.group() == "iig" ## \d = digit char, \w = word char match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123" match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"
Répétition
Les choses deviennent plus intéressantes lorsque vous utilisez + et * pour spécifier la répétition dans le motif
- + — 1 ou plusieurs occurrences du motif à sa gauche, par exemple ‘i+’ = un ou plusieurs i
- * — 0 ou plusieurs occurrences du motif à sa gauche
- ? — correspond à 0 ou 1 occurrence du motif à sa gauche
Leftmost & Largest
Dans un premier temps, la recherche trouve la correspondance la plus à gauche pour le motif, et dans un second temps, elle essaie d’utiliser la plus grande partie possible de la chaîne de caractères — c’est-à-dire. + et * vont le plus loin possible (on dit que les + et * sont « gourmands »).
Exemples de répétition
## i+ = one or more i's, as many as possible. match = re.search(r'pi+', 'piiig') # found, match.group() == "piii" ## Finds the first/leftmost solution, and within it drives the + ## as far as possible (aka 'leftmost and largest'). ## In this example, note that it does not get to the second set of i's. match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii" ## \s* = zero or more whitespace chars ## Here look for 3 digits, possibly separated by whitespace. match = re.search(r'\d\s*\d\s*\d', 'xx1 2 3xx') # found, match.group() == "1 2 3" match = re.search(r'\d\s*\d\s*\d', 'xx12 3xx') # found, match.group() == "12 3" match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123" ## ^ = matches the start of string, so this fails: match = re.search(r'^b\w+', 'foobar') # not found, match == None ## but without the ^ it succeeds: match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"
Exemple d’emails
Supposons que vous voulez trouver l’adresse email à l’intérieur de la chaîne ‘xyz [email protected] purple monkey’. Nous allons utiliser cet exemple pour démontrer d’autres fonctionnalités des expressions régulières. Voici une tentative utilisant le motif r’\w+@\w+’:
str = 'purple [email protected] monkey dishwasher' match = re.search(r'\w+@\w+', str) if match: print match.group() ## 'b@google'
La recherche n’obtient pas l’adresse électronique complète dans ce cas car le \w ne correspond pas au ‘-‘ ou au ‘.’ de l’adresse. Nous allons corriger cela en utilisant les fonctionnalités d’expression régulière ci-dessous.
Crochets
Les crochets peuvent être utilisés pour indiquer un ensemble de caractères, donc correspond à ‘a’ ou ‘b’ ou ‘c’. Les codes \w, \s etc. fonctionnent également à l’intérieur des crochets à la seule exception que le point (.) signifie simplement un point littéral. Pour le problème des e-mails, les crochets sont un moyen facile d’ajouter ‘.’ et ‘-‘ à l’ensemble des caractères qui peuvent apparaître autour du @ avec le motif r’+@+’ pour obtenir l’adresse e-mail complète:
match = re.search(r'+@+', str) if match: print match.group() ## '[email protected]'
(Autres caractéristiques des crochets) Vous pouvez également utiliser un tiret pour indiquer une plage, ce qui correspond à toutes les lettres minuscules. Pour utiliser un tiret sans indiquer un intervalle, mettez le tiret en dernier, par exemple . Un chapeau haut (^) au début d’un ensemble de crochets l’inverse, donc correspond à tout caractère sauf ‘a’ ou ‘b’.
Extraction de groupe
La fonctionnalité « groupe » d’une expression régulière vous permet de sélectionner des parties du texte correspondant. Supposons pour le problème des emails que nous voulions extraire le nom d’utilisateur et l’hôte séparément. Pour ce faire, ajoutez des parenthèses ( ) autour du nom d’utilisateur et de l’hôte dans le motif, comme ceci : r'(+)@(+)’. Dans ce cas, les parenthèses ne modifient pas ce que le motif va rechercher, mais elles établissent des « groupes » logiques à l’intérieur du texte de recherche. Si la recherche aboutit, match.group(1) est le texte correspondant à la première parenthèse gauche, et match.group(2) est le texte correspondant à la deuxième parenthèse gauche. Le simple match.group() est toujours le texte de correspondance entier comme d’habitude.
str = 'purple [email protected] monkey dishwasher' match = re.search(r'(+)@(+)', str) if match: print match.group() ## '[email protected]' (the whole match) print match.group(1) ## 'alice-b' (the username, group 1) print match.group(2) ## 'google.com' (the host, group 2)
Un flux de travail commun avec les expressions régulières est que vous écrivez un motif pour la chose que vous recherchez, en ajoutant des groupes de parenthèses pour extraire les parties que vous voulez.
findall
findall() est probablement la fonction unique la plus puissante du module re. Plus haut, nous avons utilisé re.search() pour trouver la première correspondance d’un motif. findall() trouve *toutes* les correspondances et les renvoie sous forme de liste de chaînes de caractères, chaque chaîne représentant une correspondance.
## Suppose we have a text with many email addresses str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## Here re.findall() returns a list of all the found email strings emails = re.findall(r'+@+', str) ## for email in emails: # do something with each found email string print email
findall With Files
Pour les fichiers, vous avez peut-être l’habitude d’écrire une boucle pour itérer sur les lignes du fichier, et vous pourriez ensuite appeler findall() sur chaque ligne. Au lieu de cela, laissez findall() faire l’itération pour vous — beaucoup mieux ! Alimentez simplement le texte entier du fichier dans findall() et laissez-le retourner une liste de toutes les correspondances en une seule étape (rappelez-vous que f.read() retourne le texte entier d’un fichier dans une seule chaîne):
# Open file f = open('test.txt', 'r') # Feed the file text into findall(); it returns a list of all the found strings strings = re.findall(r'some pattern', f.read())
findall et groupes
Le mécanisme de groupe de parenthèses ( ) peut être combiné avec findall(). Si le motif comprend 2 groupes de parenthèses ou plus, alors au lieu de retourner une liste de chaînes de caractères, findall() retourne une liste de *tuples*. Chaque tuple représente une correspondance du motif, et à l’intérieur du tuple se trouvent les données group(1), group(2) …. Ainsi, si 2 groupes de parenthèses sont ajoutés au motif email, alors findall() renvoie une liste de tuples, chaque longueur 2 contenant le nom d’utilisateur et l’hôte, par exemple (‘alice’, ‘google.com’).
str = 'purple [email protected], blah monkey [email protected] blah dishwasher' tuples = re.findall(r'(+)@(+)', str) print tuples ## for tuple in tuples: print tuple ## username print tuple ## host
Une fois que vous avez la liste de tuples, vous pouvez boucler dessus pour effectuer un calcul pour chaque tuple. Si le motif ne comprend pas de parenthèses, alors findall() renvoie une liste de chaînes de caractères trouvées comme dans les exemples précédents. Si le motif comprend un seul groupe de parenthèses, alors findall() retourne une liste de chaînes de caractères correspondant à ce seul groupe. (Caractéristique optionnelle obscure : Parfois vous avez des groupes de parenthèses ( ) dans le motif, mais que vous ne voulez pas extraire. Dans ce cas, écrivez les parens avec un ? : au début, par exemple (? : ) et cette paren gauche ne comptera pas comme un résultat de groupe.)
RE Workflow and Debug
Les patterns d’expression régulière emballent beaucoup de sens dans seulement quelques caractères , mais ils sont si denses que vous pouvez passer beaucoup de temps à déboguer vos patterns. Configurez votre moteur d’exécution de sorte que vous puissiez exécuter un motif et imprimer ce qu’il correspond facilement, par exemple en l’exécutant sur un petit texte de test et en imprimant le résultat de findall(). Si le motif ne correspond à rien, essayez de l’affaiblir, en supprimant des parties du motif pour obtenir trop de correspondances. Lorsque le motif ne correspond à rien, vous ne pouvez pas progresser car il n’y a rien de concret à regarder. Une fois qu’il correspond à trop de choses, alors vous pouvez travailler à le resserrer de façon incrémentielle pour atteindre juste ce que vous voulez.
Options
Les fonctions re prennent des options pour modifier le comportement du pattern match. Le drapeau de l’option est ajouté comme un argument supplémentaire à la search() ou findall() etc, par exemple re.search(pat, str, re.IGNORECASE).
- IGNORECASE — ignore les différences majuscules/minuscules pour la correspondance, ainsi ‘a’ correspond à la fois à ‘a’ et à ‘A’.
- DOTALL — permet au point (.) de correspondre à une nouvelle ligne — normalement il correspond à tout sauf à une nouvelle ligne. Cela peut vous faire trébucher — vous pensez que .* correspond à tout, mais par défaut, il ne va pas au-delà de la fin d’une ligne. Notez que \s (espace blanc) inclut les nouvelles lignes, donc si vous voulez correspondre à une suite d’espace blanc qui peut inclure une nouvelle ligne, vous pouvez simplement utiliser \s*
- MULTILINE — Dans une chaîne faite de plusieurs lignes, autorisez ^ et $ à correspondre au début et à la fin de chaque ligne. Normalement, ^/$ correspondrait juste au début et à la fin de la chaîne entière.
Greedy vs. Non-Greedy (facultatif)
Cette section facultative montre une technique d’expression régulière plus avancée qui n’est pas nécessaire pour les exercices.
Supposons que vous ayez un texte avec des balises : <b>foo</b> et <i>ainsi de suite</i>
Supposons que vous essayez de faire correspondre chaque balise avec le motif ‘(<.*>)’ — qu’est-ce qui correspond en premier ?
Le résultat est un peu surprenant, mais l’aspect gourmand du .* fait qu’il correspond à l’ensemble de ‘<b>foo</b> et <i>ainsi</i>’ comme une seule grande correspondance. Le problème est que le .* va aussi loin qu’il le peut, au lieu de s’arrêter au premier > (aka il est « gourmand »).
Il y a une extension à l’expression régulière où vous ajoutez un ? à la fin, comme .* ? ou .+ ?, les changeant pour être non gourmands. Maintenant, elles s’arrêtent dès qu’elles le peuvent. Ainsi, le motif ‘(<.*?>)’ obtiendra juste ‘<b>’ comme première correspondance, et ‘</b>’ comme deuxième correspondance, et ainsi de suite en obtenant chaque paire <..> à son tour. Le style est typiquement que vous utilisez un .* ?, puis immédiatement à sa droite cherchez un marqueur concret (> dans ce cas) qui force la fin de l’exécution du .* ?.
L’extension * ? est originaire de Perl, et les expressions régulières qui incluent les extensions de Perl sont connues sous le nom d’expressions régulières compatibles Perl — pcre. Python inclut le support de pcre. De nombreux utilitaires de ligne de commande, etc. ont un drapeau où ils acceptent les motifs pcre.
Une technique plus ancienne mais largement utilisée pour coder cette idée de « tous ces caractères sauf s’arrêter à X » utilise le style crochet. Pour ce qui précède, vous pourriez écrire le motif, mais au lieu de .* pour obtenir tous les caractères, utiliser * qui saute tous les caractères qui ne sont pas > (le ^ de tête « inverse » le jeu de crochets, de sorte qu’il correspond à tout caractère qui n’est pas dans les crochets).
Substitution (facultatif)
La fonction re.sub(pat, replacement, str) recherche toutes les instances du motif dans la chaîne donnée, et les remplace. La chaîne de remplacement peut inclure ‘\1’, ‘\2’ qui font référence au texte du groupe(1), du groupe(2), et ainsi de suite à partir du texte original correspondant.
Voici un exemple qui recherche toutes les adresses électroniques, et les change pour garder l’utilisateur (\1) mais avoir yo-yo-dyne.com comme hôte.
str = 'purple [email protected], blah monkey [email protected] blah dishwasher' ## re.sub(pat, replacement, str) -- returns new string with all replacements, ## is group(1), group(2) in the replacement print re.sub(r'(+)@(+)', r'@yo-yo-dyne.com', str) ## purple [email protected], blah monkey [email protected] blah dishwasher
Exercice
Pour pratiquer les expressions régulières, voir l’exercice sur les noms de bébé.