7.2. Exemple : adresses postales
Cette série d’exemples est inspirée d’un problème réel que j’ai
eu au cours de mon travail, l’extraction et la standardisation
d’adresses postales exportées d’un ancien système avant de les importer
dans un nouveau système (vous voyez, je n’invente rien, c’est
réellement utile). L’exemple suivant montre comment j’ai abordé ce problème.
Exemple 7.1. Reconnaître la fin d’une chaîne
>>> s = '100 NORTH MAIN ROAD'
>>> s.replace('ROAD', 'RD.')
'100 NORTH MAIN RD.'
>>> s = '100 NORTH BROAD ROAD'
>>> s.replace('ROAD', 'RD.')
'100 NORTH BRD. RD.'
>>> s[:-4] + s[-4:].replace('ROAD', 'RD.')
'100 NORTH BROAD RD.'
>>> import re
>>> re.sub('ROAD$', 'RD.', s)
'100 NORTH BROAD RD.'
|
Mon but était de standardiser les adresses de manière à ce
que 'ROAD' soit toujours abrégé en
'RD.'. Au premier abord, je pensais que ce
serait assez simple pour utiliser uniquement la méthode de chaîne
replace. Après tout, toutes les données
étaient déjà en majuscules, donc les erreurs de casses ne seraient
pas un problème. De plus, la chaîne de recherche,
'ROAD', était une constante. Pour cet exemple
trompeusement simple, s.replace fonctionne
effectivement.
|
|
Malheureusement, la vie est pleine de contre-exemples et je
découvrais assez rapidemment celui-ci. Le problème ici est que
'ROAD' apparaît deux fois dans l’adresse,
d’abord comme partie du nom de la rue 'BROAD'
et ensuite comme mot isolé. La méthode replace
trouve ces deux occurences et les remplace aveuglément, rendant
l’adresse illisible.
|
|
Pour résoudre le problème des adresses comprenant plus d’une
sous-chaîne 'ROAD', nous pourrions recourir à
quelque chose de ce genre : ne rechercher et remplacer
'ROAD' que dans les 4 derniers caractères de
l’adresse (s[-4:]) et ignorer le début de la
chaîne (s[:-4]). Mais on voit bien que ça
commence à être embrouillé. Par exemple, le motif dépend de la
longueur de la chaîne que nous remplaçons (si nous remplaçons
'STREET' par 'ST.', nous
devons écrire s[:-6] et
s[-6:].replace(...)). Aimeriez-vous revenir à
ce code dans six mois et devoir le débugger ? En ce qui me
concerne, certainement pas.
|
|
Il est temps de recourir aux expressions régulières. En
Python, toutes les fonctionalités en
rapport aux expressions régulières sont contenues dans le module
re.
|
|
Regardez le premier paramètre, 'ROAD$'.
C’est une expression régulière très simple qui ne reconnaît
'ROAD' que s’il apparaît à la fin d’une chaîne.
Le symbole $ signifie «fin de la
chaîne» (il y a un caractère correspondant, l’accent
circonflexe ^, qui signifie «début de la
chaîne»).
|
|
A l’aide de la fonction re.sub, nous
recherchons dans la chaîne s l’expression
régulière 'ROAD$' et la remplaçons par
'RD.'. Cela correspond à
ROAD à la fin de la chaîne
s, mais ne correspond pas
au ROAD faisant partie du mot
BROAD, puisqu’il est au milieu de
s.
|
En continuant mon travail de reformatage d’adresses, je
decouvrais bientôt que le modèle précédent, reconnaître
'ROAD' à la fin de l’adresse, ne suffisait pas,
car toutes les adresses n’incluaient pas d’identifiant pour la
rue. Certaines finissaient simplement par le nom de la rue. La
plupart du temps, je m’en sortais sans problème, mais si le nom de
la rue était 'BROAD', alors l’expression
régulière reconnaissait 'ROAD' à la fin de la
chaîne dans le mot 'BROAD'. Ce n’était pas ce
que je voulais.
Exemple 7.2. Reconnaître des mots entiers
>>> s = '100 BROAD'
>>> re.sub('ROAD$', 'RD.', s)
'100 BRD.'
>>> re.sub('\\bROAD$', 'RD.', s)
'100 BROAD'
>>> re.sub(r'\bROAD$', 'RD.', s)
'100 BROAD'
>>> s = '100 BROAD ROAD APT. 3'
>>> re.sub(r'\bROAD$', 'RD.', s)
'100 BROAD ROAD APT. 3'
>>> re.sub(r'\bROAD\b', 'RD.', s)
'100 BROAD RD. APT 3'
|
Ce que je voulais vraiment était de
reconnaître 'ROAD' quand il était à la fin de
la chaîne et qu’il était un mot isolé, pas
une partie de mot. Pour exprimer cela dans une expressions
régulière, on utilise \b, qui signifie
«une limite de mot doit apparaître ici». En
Python, c'est rendu plus compliqué
par le fait que le caractère '\', qui est le
caractère d’échappement, doit lui-même être précédé du caractère
d’échappement (c'est ce qui est parfois appelé la
backslash plague et c’est une des
raison pour lesquelles les expressions régulières sont plus
faciles à utliser en Perl qu’en
Python. Par contre,
Perl mélange les expressions régulières
et la syntaxe du langage, donc si vous avez un bogue, il peut être
difficile de savoir si c’est une erreur dans la syntaxe ou dans
l’expression régulière).
|
|
Pour éviter la backslash
plague, vous pouvez utiliser ce qu’on appelle une
chaîne brute, en préfixant la chaîne par
la lettre r. Cela signale à
Python que cette chaîne doit être
traitée sans échappement, '\t' est un caractère
de tabulation, mais r'\t' est réellement un
caractère backslash
\ suivi de la lettre t. Je
vous conseille de toujours utiliser des chaînes brutes lorsque
vous employez des expressions régulières, sinon cela devient
confus très vite (et les expressions régulières peuvent devenir
suffisament confuses par elles-mêmes).
|
|
*soupir* Malheureusement, je découvrais
rapidement d’autres cas qui contredisaient mon raisonnement. Dans
le cas présent, l’adresse contenait le mot isolé
'ROAD' mais il n’était pas à la fin de la
chaîne, car l’adresse avait un numéro d’appartement après
l’identifiant de la rue. Comme 'ROAD' n’était
pas tout à la fin de la chaîne, il n’était pas identifié, donc
l’appel de re.sub s’achèvait sans rien
remplacer, j’obtenais en retour la chaîne d’origine, ce qui
n’était pas le but recherché.
|
|
Pour résoudre ce problème, j’enlevais le caractère
$et ajoutais un deuxième \b.
L’expression régulière signifiait alors «reconnaître
'ROAD' lorsqu’il est un mot isolé, n’importe où
dans la chaîne», que ce soit à la fin, au début ou quelque
part au milieu.
|