Chapitre 13 : Expressions régulières et parsing

         1 – Définition et syntaxe

    Une expression régulière est une suite de caractères qui a pour but de décrire un fragment de texte. Elle est constitué de deux types de caractères:

Les caractères dits normaux.
Les métacaractères ayant une signification particulière, par exemple ^ signifie début de ligne et non pas le caractère «chapeau »littéral.
Certains programmes Unix comme egrep, sed ou encore awk savent interpréter les expressions régulières. Tous ces programmes fonctionnent généralement selon le schéma suivant:

Le programme lit un fichier ligne par ligne.
Pour chaque ligne lue, si l’expression régulière passée en argument est présente alors le programme effectue une action.
Par exemple, pour le programme egrep :

[fuchs@rome cours_python]$ egrep « ^DEF » herp_virus.gbk
DEFINITION Human herpesvirus 2, complete genome.
[fuchs@rome cours_python]$
Ici, egrep renvoie toutes les lignes du fichier genbank du virus de l’herpès (herp_virus.gbk) qui correspondent à l’expression régulière ^DEF (i.e. DEF en début de ligne).

Avant de voir comment Python gère les expressions régulières, voici quelques éléments de syntaxe des métacaractères :

^ début de chaîne de caractères ou de ligne


Exemple : l’expression ^ATG correspond à la chaîne de caractères ATGCGT mais pas à la chaîne CCATGTT.
$ fin de chaîne de caractères ou de ligne
Exemple : l’expression ATG$ correspond à la chaîne de caractères TGCATG mais pas avec la chaîne CCATGTT.


. n’importe quel caractère (mais un caractère quand même)
Exemple : l’expression A.G correspond à ATG, AtG, A4G, mais aussi à A-G ou à A G.


[ABC] le caractère A ou B ou C (un seul caractère)
Exemple : l’expression T[ABC]G correspond à TAG, TBG ou TCG, mais pas à TG.


[A-Z] n’importe quelle lettre majuscule
Exemple : l’expression C[A-Z]T correspond à CAT, CBT, CCT…


[a-z] n’importe quelle lettre minuscule


[0-9] n’importe quel chiffre


[A-Za-z0-9] n’importe quel caractère alphanumérique


[^AB] n’importe quel caractère sauf A et B
Exemple : l’expression CG[^AB]T correspond à CG9T, CGCT… mais pas à CGAT ni à CGBT.


caractère d’échappement (pour protéger certains caractères)
Exemple : l’expression + désigne le caractère + sans autre signification particulière. L’expression A.G correspond à A.G et non pas à A suivi de n’importe quel caractère, suivi de G.


* 0 à n fois le caractère précédent ou l’expression entre parenthèses précédente
Exemple : l’expression A(CG)*T correspond à AT, ACGT, ACGCGT…


+ 1 à n fois le caractère précédent ou l’expression entre parenthèses précédente
Exemple : l’expression A(CG)+T correspond à ACGT, ACGCGT… mais pas à AT.


? 0 à 1 fois le caractère précédent ou l’expression entre parenthèses précédente
Exemple : l’expression A(CG)?T correspond à AT ou ACGT.


{n} n fois le caractère précédent ou l’expression entre parenthèses précédente


{n,m} n à m fois le caractère précédent ou l’expression entre parenthèses précédente


{n,} au moins n fois le caractère précédent ou l’expression entre parenthèses précédente


{,m} au plus m fois le caractère précédent ou l’expression entre parenthèses précédente


(CG|TT) chaînes de caractères CG ou TT
Exemple : l’expression A(CG|TT)C correspond à ACGC ou ATTC.

         2 – Module re et fonction search

    Dans le module re, la fonction search() permet de rechercher un motif (pattern) au sein d’une chaîne de caractères avec une syntaxe de la forme search(motif, chaine). Si motif existe dans chaine, Python renvoie une instance MatchObject. Sans entrer dans les détails propres au langage orienté objet, si on utilise cette instance dans un test, il sera considéré comme vrai. Regardez cet exemple dans lequel on va rechercher le motif tigre dans la chaîne de caractères « girafe tigre singe »:

Code python

>>> import re
>>> animaux = « girafe tigre singe »
>>> re.search(‘tigre’, animaux)
<_sre.SRE_Match object at 0x7fefdaefe2a0>
>>> if re.search(‘tigre’, animaux):
… print « OK »

OK
Fonction match()

Il existe aussi la fonction match() dans le module re qui fonctionne sur le modèle de search(). La différence est qu’elle renvoie une instance MatchObject seulement lorsque l’expression régulière correspond (match) au début de la chaîne (à partir du premier caractère).

Code python

>>> animaux = « girafe tigre singe »
>>> re.search(‘tigre’, animaux)
<_sre.SRE_Match object at 0x7fefdaefe718>
>>> re.match(‘tigre’, animaux)
Nous vous recommandons plutôt l’usage de la fonction search(). Si vous souhaitez avoir une correspondance avec le début de la chaîne, vous pouvez toujours utiliser l’accroche de début de ligne ^.

Compilation d’expressions régulières

Il est aussi commode de préalablement compiler l’expression régulière à l’aide de la fonction compile() qui renvoie un objet de type expression régulière :

Code python

>>> regex = re.compile(« ^tigre »)
>>> regex
<_sre.SRE_Pattern object at 0x7fefdafd0df0>
On peut alors utiliser directement cet objet avec la méthode search() :

Code python

>>> animaux = « girafe tigre singe »
>>> regex.search(animaux)
>>> animaux = « tigre singe »
>>> regex.search(animaux)
<_sre.SRE_Match object at 0x7fefdaefe718>
>>> animaux = « singe tigre »
>>> regex.search(animaux)
Groupes

Python renvoie un objet MatchObject lorsqu’une expression régulière trouve une correspondance dans une chaîne pour qu’on puisse récupérer des informations sur les zones de correspondance.

Code python

>>> regex = re.compile(‘([0-9]+).([0-9]+)’)
>>> resultat = regex.search(« pi vaut 3.14 »)
>>> resultat.group(0)
‘3.14’
>>> resultat.group(1)
‘3’
>>> resultat.group(2)
’14’
>>> resultat.start()
8
>>> resultat.end()
12
Dans cet exemple, on recherche un nombre composé

de plusieurs chiffres [0-9]+,
suivi d’un point . (le point a une signification comme métacaractère, donc il faut l’échapper avec pour qu’il ait une signification de point),
suivi d’un nombre à plusieurs chiffres [0-9]+.

Les parenthèses dans l’expression régulière permettent de créer des groupes qui seront récupérés ultérieurement par la fonction group(). La totalité de la correspondance est donné par group(0), le premier élément entre parenthèse est donné par group(1) et le second par group(2).

Les fonctions start() et end() donnent respectivement la position de début et de fin de la zone qui correspond à l’expression régulière. Notez que la fonction search() ne renvoie que la première zone qui correspond à l’expression régulière, même s’il en existe plusieurs :

Code python

>>> resultat = regex.search(« pi vaut 3.14 et e vaut 2.72 »)
>>> resultat.group(0)
‘3.14’
Fonction findall()

Pour récupérer chaque zone, vous pouvez utiliser la fonction findall() qui renvoie une liste des éléments en correspondance.

Code python

>>> regex = re.compile(‘[0-9]+.[0-9]+’)
>>> resultat = regex.findall(« pi vaut 3.14 et e vaut 2.72 »)
>>> resultat
[‘3.14’, ‘2.72’]
>>> regex = re.compile(‘([0-9]+).([0-9]+)’)
>>> resultat = regex.findall(« pi vaut 3.14 et e vaut 2.72 »)
>>> resultat
[(‘3′, ’14’), (‘2′, ’72’)]
Fonction sub()

Enfin, la fonction sub() permet d’effectuer des remplacements assez puissants. Par défaut la fonction sub(chaine1,chaine2) remplace toutes les occurrences trouvées par l’expression régulière dans chaine2 par chaine1. Si vous souhaitez ne remplacer que les n premières occurrences, utilisez l’argument count=n :

>>> regex.sub(‘quelque chose’, »pi vaut 3.14 et e vaut 2.72″)
‘pi vaut quelque chose et e vaut quelque chose’
>>> regex.sub(‘quelque chose’, »pi vaut 3.14 et e vaut 2.72″, count=1)
‘pi vaut quelque chose et e vaut 2.72’
Nous espérons que vous êtes convaincus de la puissance du module re et des expressions régulières, alors à vos expressions régulières !

         3 – Exercices : extraction des gènes d’un fichier genbank

    Pour les exercices suivants, vous utiliserez le module d’expressions régulières re et le fichier genbank du chromosome I de la levure du boulanger Saccharomyces cerevisiae NC_001133.gbk.

Écrivez un script qui extrait l’organisme du fichier genbank NC_001133.gbk.
Modifiez le script précédent pour qu’il affiche toutes les lignes qui indiquent l’emplacement du début et de la fin des gènes, du type :
gene 58..272
Faites de même avec les gènes complémentaires :
gene complement(55979..56935)
Récupérez maintenant la séquence nucléique et affichez-la à l’écran. Vérifiez que vous n’avez pas fait d’erreur en comparant la taille de la séquence extraite avec celle indiquée dans le fichier genbank.
Mettez cette séquence dans une liste et récupérez les deux premiers gènes (en les affichant à l’écran). Attention, le premier gène est un gène complémentaire, n’oubliez pas de prendre le complémentaire inverse de la séquence extraite.
À partir de toutes ces petites opérations que vous transformerez en fonctions, concevez un programme genbank2fasta.py qui extrait tous les gènes d’un fichier genbank fourni en argument et les affiche à l’écran. Pour cela vous pourrez utiliser tout ce que vous avez vu jusqu’à présent (fonctions, listes, modules, etc.).
À partir du script précédent, refaites le même programme (genbank2fasta.py) en écrivant chaque gène au format fasta dans un fichier.
Pour rappel, l’écriture d’une séquence au format fasta est le suivant :

>ligne de commentaire
sequence sur une ligne de 80 caractères maxi
suite de la séquence …………………..
suite de la séquence …………………..
Vous utiliserez comme ligne de commentaire le nom de l’organisme, suivi du numero du gène, suivi des positions de début et de fin du gène, comme dans cet exemple
>Saccharomyces cerevisiae 1 1807 2169
Les noms des fichiers fasta seront de la forme gene1.fasta, gene2.fasta, etc.