17.2. plural.py, étape 1

Nous avons donc des mots, qui, en anglais du moins, sont constitués de chaînes de caractères. Par ailleurs, nous avons des règles qui disent que nous devons reconnaître différentes combinaisons de caractères, et leur faire subir certaines modifications. C'est un problème qui semble être fait pour les expressions régulières.

Exemple 17.1. plural1.py


import re

def plural(noun):                            
    if re.search('[sxz]$', noun):             1
        return re.sub('$', 'es', noun)        2
    elif re.search('[^aeioudgkprt]h$', noun):
        return re.sub('$', 'es', noun)       
    elif re.search('[^aeiou]y$', noun):      
        return re.sub('y$', 'ies', noun)     
    else:                                    
        return noun + 's'                    
1 C'est une expression régulière, mais elle utilise une syntaxe que vous n'avez pas vue au Chapitre 7, Expressions régulières. Les crochets signifient «reconnaître exactement un de ces caractères». Donc [sxz] signifie «s ou x ou z», mais seulement l'une de ces trois lettres. Le $ doit vous être familier, il reconnaît la fin de la chaîne. Il s'agit donc de vérifier si noun se termine par s, x ou z.
2 La fonction re.sub effectue des remplacements à partir d'une expression régulière. Examinons-la en détail.

Exemple 17.2. Présentation de re.sub

>>> import re
>>> re.search('[abc]', 'Mark')   1
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.sub('[abc]', 'o', 'Mark') 2
'Mork'
>>> re.sub('[abc]', 'o', 'rock') 3
'rook'
>>> re.sub('[abc]', 'o', 'caps') 4
'oops'
1 La chaîne Mark contient-elle a, b ou c ? Oui, elle contient a.
2 Maintenant, recherchons a, b ou c, et remplaçons-le par o. Mark devient Mork.
3 La même fonction transforme rock en rook.
4 Vous auriez pu croire qu'elle transformerait caps en oaps, mais ce n'est pas le cas. re.sub remplace tout ce qui a été reconnu, pas seulement la première occurrence. Cette expression régulière transforme donc caps en oops, aussi bien le c que le a sont remplacés par o.

Exemple 17.3. Retour à plural1.py


import re

def plural(noun):                            
    if re.search('[sxz]$', noun):            
        return re.sub('$', 'es', noun)        1
    elif re.search('[^aeioudgkprt]h$', noun): 2
        return re.sub('$', 'es', noun)        3
    elif re.search('[^aeiou]y$', noun):      
        return re.sub('y$', 'ies', noun)     
    else:                                    
        return noun + 's'                    
1 Retour à la fonction plural. Que faisons nous ? Nous remplaçons la fin de chaîne par es. En d'autre termes, nous ajoutons es à la chaîne. Nous pourrions accomplir la même chose avec une concaténation, par exemple noun + 'es', mais j'utilise les expressions régulières pour tous les cas pour des raisons de cohérence, raisons qui deviendront plus claires plus loin dans le chapitre.
2 Regardez attentivement, c'est encore une nouvelle variation. Le ^ comme premier caractère entre les crochets signifie quelque chose de spécial : la négation. [^abc] signifie «n'importe quel caractère unique sauf a, b ou c». Donc [^aeioudgkprt] signifie n'importe quel caractère sauf a, e, i, o, u, d, g, k, p, r ou t. Ensuite, ce caractère doit être suivi de h, suivi par la fin de chaîne. Nous cherchons les mots qui finissent par H dans lesquels le H est prononcé.
3 Même motif ici : reconnaître les mots qui finissent par Y, dans lesquels le caractère qui précède Y n'est pas a, e, i, o ou u. Nous recherchons des mots qui se finissent par un Y prononcé comme I.

Exemple 17.4. Expressions régulières avec négation

>>> import re
>>> re.search('[^aeiou]y$', 'vacancy') 1
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.search('[^aeiou]y$', 'boy')     2
>>> 
>>> re.search('[^aeiou]y$', 'day')
>>> 
>>> re.search('[^aeiou]y$', 'pita')    3
>>> 
1 vacancy est reconnu par cette expression régulière parce qu'il se termine par cy, c n'est pas a, e, i, o ou u.
2 boy n'est pas reconnu, il se termine par oy et nous avons spécifié que le caractère précédent le y ne pouvait pas être o. day n'est pas reconnu car il se termine par ay.
3 pita n'est pas reconnu parce qu'il ne se termine pas par y.

Exemple 17.5. Fonctionnement de re.sub

>>> re.sub('y$', 'ies', 'vacancy')              1
'vacancies'
>>> re.sub('y$', 'ies', 'agency')
'agencies'
>>> re.sub('([^aeiou])y$', r'\1ies', 'vacancy') 2
'vacancies'
1 Cette expression régulière transforme vacancy en vacancies et agency en agencies, ce qui est le but recherché. Notez qu'elle transformerait également boy en boies, mais cela n'arrivera jamais puisque nous faison d'abord le re.search pour savoir si nous devons effectuer le re.sub.
2 Juste au passage, je veux souligner qu'il est possible de combiner ces deux expressions régulières (une pour savoir si la règle s'applique, l'autre pour l'appliquer effectivement) en une seule expression régulière. Voilà à quoi ça ressemblerait. Elle devrait être familière : elle utilise un groupe mémorisé, ce que vous avez appris à la Section 7.6, «Etude de cas : reconnaissance de numéros de téléphone», pour se rappeler du caractère avant le y. Ensuite, dans la chaîne de substitution, elle utilise une nouvelle syntaxe, \1, qui signifie «hep, ce premier groupe que tu as mémorisé, met le ici». Dans ce cas, elle a mémorisé le c devant le y et donc elle substitue un c au c et ies à y (si on a plus d'un groupe mémorisé, on utilise \2 et \3 etc.).

Les remplacements par expressions régulières sont extrêmement puissants et la syntaxe \1 les rend encore plus puissants. Mais combiner l'opération entière en une seule expression régulière est également beaucoup plus difficile à lire et ne correspond pas à la manière dont les règles de pluriel des noms ont été définies au début. Les règles se présente de la manière suivante : «si le mot se termine par S, X ou Z, alors ajouter ES». Si vous regardez cette fonction, vous verrez deux lignes de codes qui disent «si le mot se termine par S, X ou Z, alors ajouter ES». Difficile de trouver une correspondance plus directe.