You are here: Sommaire > Plongez au coeur de Python > Refactorisation > Refactorisation | << >> | ||||
Plongez au coeur de PythonDe débutant à expert |
Le meilleur avec des tests unitaires exhaustifs, ce n’est pas le sentiment que vous avez quand tous vos cas de test finissent par passer, ni même le sentiment que vous avez quand quelqu’un vous reproche d’avoir endommagé leur code et que vous pouvez véritablement prouver que vous ne l’avez pas fait. Le meilleur, c’est que les tests unitaires vous permettent la refactorisation continue de votre code.
La refactorisation est le processus par lequel on fait d’un code qui fonctionne un code qui fonctionne mieux. Souvent, «mieux» signifie «plus vite», bien que cela puisse aussi vouloir dire «avec moins de mémoire», «avec moins d’espace disque» ou simplement «de manière plus élégante». Quoi que cela signifie pour vous, pour votre projet, dans votre environnement, la refactorisation est importante pour la santé à long terme de tout programme.
Ici, «mieux» veut dire «plus vite». Plus précisément, la fonction fromRoman est plus lente qu’elle ne le devrait, à cause de cette énorme expression régulière que nous utilisons pour valider les nombres romains. Cela ne vaut sans doute pas la peine de se priver complètement de l’expression régulière (cela serait difficile et ne serait pas forcément plus rapide), mais nous pouvons rendre la fonction plus rapide en précompilant l’expression régulière.
>>> import re >>> pattern = '^M?M?M?$' >>> re.search(pattern, 'M') <SRE_Match object at 01090490> >>> compiledPattern = re.compile(pattern) >>> compiledPattern <SRE_Pattern object at 00F06E28> >>> dir(compiledPattern) ['findall', 'match', 'scanner', 'search', 'split', 'sub', 'subn'] >>> compiledPattern.search('M') <SRE_Match object at 01104928>
A chaque fois que vous allez utiliser une expression régulière plus d’une fois, il vaut mieux la compiler pour obtenir un objet motif et appeler ses méthodes directement. |
Ce fichier est disponible dans le sous-répertoire py/roman/stage8/ du répertoire des exemples.
Si vous ne l’avez pas déjà fait, vous pouvez télécharger cet exemple ainsi que les autres exemples du livre.
# toRoman and rest of module omitted for clarity romanNumeralPattern = \ re.compile('^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$') def fromRoman(s): """convert Roman numeral to integer""" if not s: raise InvalidRomanNumeralError, 'Input can not be blank' if not romanNumeralPattern.search(s): raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result
Mais à quel point est-ce plus rapide de compiler notre expression régulière ? Voyez vous-même :
............. ---------------------------------------------------------------------- Ran 13 tests in 3.385s OK
Juste une note en passant : cette fois, j’ai lancé le test unitaire sans l’option -v, donc au lieu d’avoir la doc string complète pour chaque test, nous avons un point pour chaque test qui passe. (Si un test échouait, nous aurions un F (failed) et si il y avait une erreur, nous aurions un E. Nous aurions quand même la trace de pile pour chaque échec ou erreur de manière à pouvoir localiser les problèmes.) | |
Nous avons exécuté 13 tests en 3,385 secondes, au lieu de 3,685 secondes sans précompilation de l’expression régulière. C’est une amélioration de 8% et rappelez-vous que la plus grande partie du temps passé dans le test unitaire est consacré à d’autres chose. (J’ai testé séparément l’expression régulière et j’ai découvert que sa compilation accélère la fonction search de 54% en moyenne.) Pas mal pour une modification aussi simple. | |
Oh, au cas ou vous vous le demandiez, la précompilation de l’expression régulière n’a rien endommagé et nous venons de le prouver. |
Il y a une autre optimisation que je veux essayer. Etant donnée la complexité de la syntaxe des expressions régulières, il n’est pas étonnant qu’il y ait souvent plus d’une manière d’écrire la même expression. Après une discussion à propos de ce module sur comp.lang.python, quelqu’un m’a suggéré d’utiliser la syntaxe {m,n} pour des caractères répétés optionnels.
Ce fichier est disponible dans le sous-répertoire py/roman/stage8/ du répertoire des exemples.
Si vous ne l’avez pas déjà fait, vous pouvez télécharger cet exemple ainsi que les autres exemples du livre.
# rest of program omitted for clarity #old version #romanNumeralPattern = \ # re.compile('^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$') #new version romanNumeralPattern = \ re.compile('^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$')
Cette forme d’expression régulière est un petit peu plus courte (mais pas plus lisible). La question est, est-elle plus rapide ?
............. ---------------------------------------------------------------------- Ran 13 tests in 3.315s OK
Il y a une autre modification que j’aimerais faire et ensuite je promet que j’arrêterai de refactoriser ce module. Comme nous l’avons vu de manière répétée, les expressions régulières peuvent devenir emberlificotées et illisibles assez vite. Je voudrais pouvoir revenir à ce module dans six mois et être capable de le maintenir. Bien sûr les cas de tests passent, je sais donc qu’il fonctionne mais si je ne peux pas comprendre comment il fonctionne, je ne serai pas capable d’ajouter des fonctionnalités, de corriger de nouveaux bogues et plus généralement de le maintenir. Comme nous l’avons vu au Section 7.5, «Expressions régulières détaillées», Python fournit une manière de documenter vos expressions régulières ligne à ligne.
Ce fichier est disponible dans le sous-répertoire py/roman/stage8/ du répertoire des exemples.
Si vous ne l’avez pas déjà fait, vous pouvez télécharger cet exemple ainsi que les autres exemples du livre.
# rest of program omitted for clarity #old version #romanNumeralPattern = \ # re.compile('^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$') #new version romanNumeralPattern = re.compile(''' ^ # beginning of string M{0,4} # thousands - 0 to 4 M's (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), # or 500-800 (D, followed by 0 to 3 C's) (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), # or 50-80 (L, followed by 0 to 3 X's) (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), # or 5-8 (V, followed by 0 to 3 I's) $ # end of string ''', re.VERBOSE)
............. ---------------------------------------------------------------------- Ran 13 tests in 3.315s OK
<< Gestion des changements de spécification |
| 1 | 2 | 3 | 4 | 5 | |
Postscriptum >> |