Python >> Tutoriel Python >  >> Python Tag >> RegEx

Comment trouver toutes les lignes ne contenant pas de Regex en Python ?

Aujourd'hui, je suis tombé sur ce beau problème de regex :

Données sont une chaîne multiligne et un modèle regex. Comment trouver toutes les lignes qui ne contiennent PAS le modèle regex ?

Je vais vous donner une réponse courte et une réponse longue.

La réponse courte :

Utilisez le modèle '((?!regex).)*' pour faire correspondre toutes les lignes qui ne contiennent pas le modèle regex regex . L'expression '(?! ...)' est une anticipation négative qui garantit que le modèle inclus ... ne découle pas de la position actuelle.

Parlons donc plus en détail de cette solution. (Vous pouvez également regarder ma vidéo explicative si vous préférez le format vidéo.)

Article connexe :

  • Python Regex Superpower – Le guide ultime

Voulez-vous maîtriser la superpuissance des regex ? Découvrez mon nouveau livre La façon la plus intelligente d'apprendre les expressions régulières en Python avec l'approche innovante en 3 étapes pour un apprentissage actif :(1) étudiez un chapitre de livre, (2) résolvez un puzzle de code et (3) regardez une vidéo de chapitre éducatif.

Exemple détaillé

Considérons un extrait de code pratique. Je vais d'abord vous montrer le code et l'expliquer ensuite :

import re
s = '''the answer is 42
the answer: 42
42 is the answer
43 is not
the answer
42'''

for match in re.finditer('^((?!42).)*$', s, flags=re.M):
    print(match)

	
'''
<re.Match object; span=(49, 58), match='43 is not'>
<re.Match object; span=(59, 69), match='the answer'>
'''

Vous pouvez voir que le code correspond avec succès uniquement aux lignes qui ne contiennent pas la chaîne '42' .

Comment faire correspondre une ligne qui ne contient pas de chaîne ?

L'idée générale est de faire correspondre une ligne qui ne contient pas la chaîne '42' , imprimez-le sur le shell et passez à la ligne suivante.

Le re.finditer(pattern, string) accomplit cela facilement en renvoyant un itérateur sur tous les objets de correspondance.

Le modèle regex '^((?!42).)*$' correspond à toute la ligne à partir de la première position '^' à la dernière position '$' .

📄 Tutoriel associé  :Si vous avez besoin d'un rappel sur les métacaractères de début et de fin de ligne, lisez ce didacticiel de 5 minutes.

Vous faites correspondre un nombre arbitraire de caractères entre les deux :le quantificateur astérisque le fait pour vous.

📄 Tutoriel associé  :Si vous avez besoin d'aide pour comprendre le quantificateur astérisque, consultez ce didacticiel de blog.

A quels personnages correspondez-vous ? Uniquement ceux où vous n'avez pas le mot négatif '42' dans votre anticipation.

📄 Tutoriel associé  :Si vous avez besoin d'un rappel sur les prévisions, consultez ce didacticiel.

L'anticipation elle-même ne consomme pas de caractère. Ainsi, vous devez le consommer manuellement en ajoutant le métacaractère point . qui correspond à tous les caractères sauf le caractère de saut de ligne '\n' .

📄 Tutoriel associé  :Il s'avère qu'il existe également un didacticiel de blog sur le métacaractère point.

Enfin, vous devez définir le re.MULTILINE drapeau, en bref :re.M , car il permet le démarrage ^ et fin $ métacaractères à faire correspondre également au début et à la fin de chaque ligne (pas seulement au début et à la fin de chaque chaîne).

📄 Tutoriel associé  :Vous pouvez en savoir plus sur l'argument des drapeaux dans ce tutoriel de blog.

Ensemble, cette expression régulière correspond à toutes les lignes qui ne contiennent pas le mot spécifique '42' .

Au cas où vous auriez des problèmes pour comprendre le concept d'anticipation (et pourquoi il ne consomme rien), jetez un œil à cette explication du tutoriel du groupe de correspondance sur ce blog :

Anticipation positive (?=…)

Le concept d'anticipation est très puissant. Tout codeur avancé devrait le savoir.

Un ami m'a récemment dit qu'il avait écrit une regex compliquée qui ignore l'ordre d'occurrence de deux mots dans un texte donné.

C'est un problème difficile, et sans le concept d'anticipation, le code résultant sera compliqué et difficile à comprendre. Cependant, le concept d'anticipation rend ce problème simple à écrire et à lire.

Mais tout d'abord :comment fonctionne l'assertion d'anticipation ?

Dans le traitement normal des expressions régulières, la regex est mise en correspondance de gauche à droite. Le moteur regex "consomme" des sous-chaînes partiellement correspondantes. La sous-chaîne consommée ne peut être mise en correspondance avec aucune autre partie de la regex.

Schéma : Un exemple simple d'anticipation. Le moteur d'expressions régulières correspond (« consomme ») la chaîne partiellement. Ensuite, il vérifie si le modèle restant peut être mis en correspondance sans réellement le faire correspondre.

Considérez l'assertion d'anticipation comme une non consommatrice correspondance de modèle.

Le moteur regex recherche le motif de gauche à droite. A chaque étape, il maintient une position « courante » pour vérifier si cette position est la première position du match restant.

En d'autres termes, le moteur regex essaie de "consommer" le caractère suivant comme une correspondance (partielle) du modèle.

L'avantage de l'expression d'anticipation est qu'elle ne consomme rien. Il « anticipe » simplement à partir de la position actuelle si ce qui suit correspondrait théoriquement au modèle d'anticipation.

Si ce n'est pas le cas, le moteur regex ne peut pas continuer.

Ensuite, il "revient en arrière" - ce qui est juste une façon élégante de dire :il revient à une décision précédente et essaie de correspondre à autre chose.

Exemple d'anticipation positive :comment faire correspondre deux mots dans un ordre arbitraire ?

Formulation du problème  :Et si vous voulez rechercher un texte donné pour le motif A ET motif B - mais sans ordre particulier ? Si les deux modèles apparaissent n'importe où dans la chaîne, la chaîne entière doit être renvoyée comme correspondance.

Maintenant, c'est un peu plus compliqué car tout modèle d'expression régulière est ordonné de gauche à droite.

Une solution simple consiste à utiliser l'assertion d'anticipation (?.*A) pour vérifier si l'expression régulière A apparaît n'importe où dans la chaîne.

Notez que nous supposons une seule chaîne de ligne comme .* le motif ne correspond pas au caractère de saut de ligne par défaut.

Tout d'abord, regardez la solution minimale pour vérifier deux modèles n'importe où dans la chaîne (par exemple, les modèles 'hi' ET 'you' ).

>>> import re
>>> pattern = '(?=.*hi)(?=.*you)'
>>> re.findall(pattern, 'hi how are yo?')
[]
>>> re.findall(pattern, 'hi how are you?')
['']

Dans le premier exemple, les deux mots n'apparaissent pas. Dans le deuxième exemple, oui.

Revenons à l'expression (?=.*hi)(?=.*you) pour faire correspondre les chaînes qui contiennent à la fois 'hi' et 'you' . Pourquoi ça marche ?

La raison en est que les expressions d'anticipation ne consomment rien. Vous recherchez d'abord un nombre arbitraire de caractères .* , suivi du mot hi .

Mais parce que le moteur regex n'a rien consommé, il est toujours à la même position au début de la chaîne . Ainsi, vous pouvez répéter la même chose pour le mot you .

Notez que cette méthode ne tient pas compte de l'ordre des deux mots :

>>> import re
>>> pattern = '(?=.*hi)(?=.*you)'
>>> re.findall(pattern, 'hi how are you?')
['']
>>> re.findall(pattern, 'you are how? hi!')
['']

Peu importe le mot "hi" ou "you" apparaît en premier dans le texte, le moteur regex trouve les deux.

Vous pouvez demander :pourquoi la sortie est-elle une chaîne vide ?

La raison en est que le moteur regex n'a consommé aucun caractère. Il vient de vérifier les prévisions.

La solution la plus simple consiste donc à utiliser tous les caractères comme suit :

>>> import re
>>> pattern = '(?=.*hi)(?=.*you).*'
>>> re.findall(pattern, 'you fly high')
['you fly high']

Maintenant, la chaîne entière est une correspondance car après avoir vérifié l'anticipation avec '(?=.*hi)(?=.*you)' , vous consommez également toute la chaîne '.*' .

Anticipation négative (?!…)

L'anticipation négative fonctionne exactement comme l'anticipation positive, sauf qu'elle vérifie que le modèle regex donné ne le fait pas se produisent à partir d'une certaine position.

Voici un exemple :

>>> import re
>>> re.search('(?!.*hi.*)', 'hi say hi?')
<re.Match object; span=(8, 8), match=''>

Le modèle d'anticipation négatif (?!.*hi.*) garantit que, en avançant dans la chaîne, il n'y a pas d'occurrence de la sous-chaîne 'hi' .

La première position où cela tient est la position 8 (juste après le deuxième 'h' ).

Comme l'anticipation positive, l'anticipation négative ne consomme aucun caractère, le résultat est donc la chaîne vide (qui est une correspondance valide du modèle).

Vous pouvez même combiner plusieurs anticipations négatives comme ceci :

>>> re.search('(?!.*hi.*)(?!\?).', 'hi say hi?')
<re.Match object; span=(8, 9), match='i'>

Vous recherchez un poste où ni 'hi' est dans l'anticipation, et le caractère de point d'interrogation ne suit pas immédiatement. Cette fois, nous consommons un caractère arbitraire, donc la correspondance résultante est le caractère 'i' .