Cours Python avec exercices écrit par Christophe Darmangeat dans le cadre du M2 PiSE |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||
Les expressions régulièresQue signifie ce terme un peu abscons ? Il désigne, de manière générale, un ensemble de « jokers » pouvant servir à vérifier si une donnée correspond à une certaine forme. Tout bêtement, si c'est un nom propre, commence-t-il effectivement par une majuscule ? Si c'est un code postal, comprend-il effectivement cinq chiffres ? Si c'est un mot de passe, fait-il au minimum 12 caractères, dont au moins une majuscule et un chiffre ? Etc. La première chose à signaler est que les expressions régulières ne sont absolument pas une particularité spécifique à Python : on les retrouve dans beaucoup de langages ou de codages informatiques – les plus anciens se souviennent, émus, du ms-dos, de ses * et de ses ? pour désigner des noms de fichiers. On peut même en trouver quelques équivalents (rudimentaires) dans d'autres contextes : ainsi, au Scrabble, où le jeton vierge (joker) remplace n'importe quelle lettre de l'alphabet. La seconde chose est que comme très souvent en informatique, le principe de base est extrêmement simple, mais en combinant ces éléments simples, on peut assez vite arriver à des problèmes d'une complexité redoutable. Avertissement : quand on travaille avec les expressions régulières, il faut toujours veiller à deux possibilités d'erreurs : la première, à laquelle on pense en premier, est celle de l'expression qui « n'attrape pas » tout ou partie des informations qu'elle est censée coder. Mais il ne faut jamais perdre de vue l'erreur inverse : celle de l'expression qui valide des informations qui ne sont pas pertinentes. On prendra donc toujours soin de tester ses expressions par rapport à ces deux erreurs possibles. Avertissement bis : faut-il le préciser, en matière d'erreurs, il est toujours possible d'avoir « fromage et dessert », et ainsi d'écrire une expression qui parvient à la fois à ne pas récupérer toutes les informations pertinentes et à récuperer certaines informations qui ne le sont pas... Premiers élémentsPour jouer avec les expression régulières, le moyen le plus direct est d'utiliser la bibliothèque adéquate, à savoir re (comme regular expression) : import re Cette bibliothèque offre en particulier la méthode search, qui renvoie un booléen si l'argument 1 figure dans l'argument 2 (c'est donc une version simplifiée du Trouve du cours d'algorithmique. Ainsi, dans une version très basique, si l'on écrit : toto = re.search("monsieur", "Bonjour monsieur") toto vaudra True tandis que tutu vaudra False. Jusque là, rien de bien nouveau, ni de bien malin. Toute l'affaire est qu'évidemment, on peut ne pas en rester là, et qu'l est possible d'utiliser des « jokers » dans ce type de situations. Les bases
Premier raffinement : l'exclusionPlacé au début d'une expression régulière, l'accent circonflexe signifie comme on vient de le voir : « début de la chaîne ». Mais placé au début d'une plage, il change totalement de sens et marque à présent l'exclusion. Ainsi, [^a-z] signifie : « n'importe quel caractère sauf une minuscule ». On peut ainsi formuler des règles dans les deux sens, soit en stipulant ce que l'on veut, soit en stipulant ce qu'on ne veut pas.Second raffinement : la gourmandisePar défaut, les codes (ou quantificateurs) des expressions régulières sont dits « gourmands », c’est-à-dire qu’ils capturent autant de caractères que possible. Ainsi, si l'on recherche l'expression "<.*>" dans le texte "Le titre est <i>Casus belli</i> !", on récupèrera la totalité du texte (le plus long extrait possible répondant à notre demande, soit <i>Casus belli</i>. Pour obtenir un résultat « non gourmand », c'est-à-dire minimal, on recherchera l'expression "<.*?>" ; on obtiendra alors "<i>". Autres opérations possiblesJusqu'ici, nous avons employé la méthode search qui effectue donc une recherche, en produisant un résultat booléen. D'autres méthodes sont disponibles, dont certaines permettent d'agir sur la chaîne traitée :
Je signale aussi, bien qu'elle soit plus dispensable :
Quelques questions-réponses pour commencer
Les séquences d'échappementCertains codes dits d'échappement (à cause de l'antislash qui les précède) remplacent avantageusement une information plus lourde, voire ouvrent des possibilités spécifiques. Voici les plus courantes :
!!! Syntaxe, priez pour nous : l'emploi du caractère \ soulève de redoutables problèmes d'interprétation par le langage. Si aucune précaution n'est prise, alors il sera probablement considéré pour lui-même, et l'expression \bchat\b, au lieu de correspondre au mot « chat », matchera avec la suite antislash – b – c – h – a – t – b – antislash. Pour faire comprendre à python que l'antislash d'une expression régulière n'est pas un caractère ordinaire mais un code à interpréter avec ce qui suit, il y a deux solutions :
Et maintenant, c'est parti, nous allons écrire les expressions régulières nous-mêmes ! Ex 1 : Premiers pas Pour cette série de questions, on écrit un petit programme qui demande une saisie au clavier et qui dit ensuite à l'utilisateur si cette saisie correspond ou non à l'expression régulière concernée. Il faut donc vérifier successivement si la chaîne entrée au clavier est bel et bien :
Quelques armes supplémentairesLes groupesUn outil supplémentaire très précieux est celui des groupes. Dans une expression régulière, un groupe définit un sous-ensemble (comme qui dirait : un groupe !) de caractères, auquel on peut appliquer des quantificateurs ou des traitements particuliers. Ainsi, (bof) désigne la suite de caractères « bof » – à ne surtout pas confondre avec [bof], qui signifie : « un b, un o ou un f ». Si l'on écrit (bof)+, le quantificateur + s'applique à l'ensemble du groupe ; autrement dit, l'expression désigne les séries de caractères qui incluent au moins une fois « bof », et donc « bof », « bofbof », « bofbofbof », etc. En fait, nous avopns déjà rencontré le groupe au passage, mais sans nous y être arrêtés, lorsque nous avons vu le booléen OU, autrement dit le | (« pipe »). Une expression peut comprendre plusieurs groupes, soit à la suite les uns des autres, soit de manière imbriquée. Outre le fait de pouvoir appliquer des quantificateurs, un des grands intérêts de cette affaire est que ces groupes sont implicitement numérotés (indicés), dans l'ordre de l'ouverture des parenthèses. On peut ainsi faire du découpage, et récupérer ainsi les morceaux de la chaîne. Prenons l'exemple d'une date au format jj/mm/aaaa. Normalement, sa recherche dans un texte ressemblera à : [0-9]{2}/[0-9]{2}/[0-9]{4} Ce qu'on peut également écrire sous la forme : \d{2}/\d{2}/\d{4} Ajoutons des groupes, et nous pourrons ensuite récupérer chaque information individuellement :
match = re.search("(\d{2})/(\d{2})/(\d{4})", texte) Les backreferencesLa présence des groupes permet également d'utiliser ces indices dans l'expression régulière elle-même. Imaginons par exemple que nous souhaitions détecter, dans un texte, la présence de mots qui se suivent à l'identique (comme dans « Je suis très très content »). Pour faire très simple, nous pouvons considérer qu'un mot est une suite composée d'un nombre quelconque de caractères, suivi d'une espace, ce qui s'écrit '[a-zA-Z]+ '. Comment repérer le fait que ce mot soit répété ? En en faisant un groupe, puis en demandant si ce groupe est suivi par lui-même (ici, le groupe étant le premier et seul de l'expression, il porte l'indice 1 : '([a-zA-Z]+) \1' NB : avec les séquences d'échappement, on peut écrire la même chose (et en fait, une chose un peu meilleure) sous la forme : '\b(\w+)\s+\1\b' Les assertions positives anticipées (positive lookahead)Cette appellation barbare désigne une idée assez simple : la possibilité de repérer des éléments dans une chaîne en quelque sorte « à l'avance », c'est-à-dire sans la parcourir. Le cas classique est celui où l'on recherche si la chaîne comporte certains éléments sans savoir dans quel ordre ceux se présentent. Imaginons le cas où nous voulons savoir si une phrase contient certains mots, dans un ordre quelconque. Avec l'approche standard, on pourrait imaginer de s'en sortir avec un OU – mais cette solution devient rapidement intenable si le nombre de mots augmente : pour 2 mots, il faut un OU, pour 3 mots il en faut 6, pour 4 mots il en faut 24, etc. C'est pourquoi le recours aux « APA » est fort utile. Pour obtenir ce résultat, la syntaxe consiste à définir l'ensemble recherché comme un groupe, et à placer au début de celui-ci la séquence ?=.*. Ainsi, si nous cherchons si un texte contient, comme celui de Rabelais, « veaux », « vaches », « cochons », « poulets », et ceci dans n'importe quel ordre, l'expression régulière correspondante sera : (?=.*\bveaux\b)(?=.*\bvaches\b)(?=.*\bcochons\b)(?=.*\bpoulets\b) Et maintenant, à vous de jouer. Ex 2 : Après les premiers pas, les suivants Dans la même lignée que l'exercice précédent, écrivez les expressions régulières permettant d'identifier :
Ex 3 : Travail en série Cette fois, pour varier un peu les plaisirs, l'application devra aller chercher un fichier texte qui contiendra les diverses propositions à raison d'une par ligne. Le programme affichera à l'écran la liste des propositions valides, et celle des propositions invalides. Pour la série concernant les mots de passe, on pourra utiliser ce fichier, et pour celle concernant les numéros de téléphone celui-ci.
Ex 4 : Nettoyage de données Vous travaillez dans une entreprise qui reçoit des milliers d’enregistrements de formulaires clients chaque mois. Ces données sont parfois mal formatées ou contiennent des erreurs. On vous demande d'écrire un code capable de nettoyer et de valider certains champs essentiels (email, numéro de téléphone, code postal, nom, etc.) afin que les données puissent être intégrées dans une base. Les données se présentent sous la forme d'un fichier texte de type csv (délimiteur : point-virgule), où chaque ligne représente un client, les champs étant dans l'ordre le nom, le prénom, le téléphone, l'adresse et le code postal. Pour tester votre code, on met à votre disposition un extrait de ce fichier. Il s'agit donc à la fois de nettoyer et de vérifier les données, qui obéiront aux conditions suivantes :
Le code devra produire deux fichiers .csv, l'un constitué des enregistrements valides (et mis au propre), l'autre des enregistrements invalides. |