You are here: Sommaire > Plongez au coeur de Python > Ecriture des tests en premier > roman.py, étape 5 | << >> | ||||
Plongez au coeur de PythonDe débutant à expert |
Maintenant que fromRoman fonctionne pour des entrées correctes, nous devons mettre en place la dernière pièce du puzzle : le faire fonctionner avec des entrées incorrectes. Cela veut dire trouver une manière d’examiner une chaîne et de déterminer si elle constitue un nombre en chiffres romains valide. C’est intrinsèquement plus difficile que de valider une entrée numérique dans toRoman, mais nous avons un outil puissant à notre disposition : les expressions régulières.
Si vous n’êtes pas familiarisé avec les expressions régulières et que vous n’avez pas lu le Chapitre 7, Expressions régulières, il est sans doute temps de le faire.
Comme nous l’avons vu au Section 7.3, «Exemple : chiffres romains», il y a plusieurs règles simples pour construire des nombres en chiffres romains à l’aide des lettres M, D, C, L, X, V et I. Récapitulons ces règles :
Ce fichier est disponible dans le sous-répertoire py/roman/stage5/ 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.
"""Convert to and from Roman numerals""" import re #Define exceptions class RomanError(Exception): pass class OutOfRangeError(RomanError): pass class NotIntegerError(RomanError): pass class InvalidRomanNumeralError(RomanError): pass #Define digit mapping romanNumeralMap = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5), ('IV', 4), ('I', 1)) def toRoman(n): """convert integer to Roman numeral""" if not (0 < n < 4000): raise OutOfRangeError, "number out of range (must be 1..3999)" if int(n) <> n: raise NotIntegerError, "non-integers can not be converted" result = "" for numeral, integer in romanNumeralMap: while n >= integer: result += numeral n -= integer return result #Define pattern to detect valid Roman numerals romanNumeralPattern = '^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 re.search(romanNumeralPattern, 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
C’est simplement l’extension du motif que nous avons vu au Section 7.3, «Exemple : chiffres romains». Les dizaines sont soit XC (90), soit XL (40), soit un L optionnel suivi de 0 à 3 X optionnels. Les unités sont soit IX (9), soit IV (4), soit un V optionnel suivi de 0 à 3 I optionnels. | |
Une fois toute cette logique encodée dans notre expression régulière, le code vérifiant la validité des nombres romain est une formalité. Si re.search retourne un objet, alors l’expression régulière à reconnu la chaîne et notre entrée est valide, sinon notre entrée est invalide. |
A ce stade, vous avez le droit d’être sceptique quant à la capacité de cette expression régulière longue et disgracieuse d’intercepter tous les types de nombres romains invalides. Mais vous n’avez pas à me croire sur parole, observez plutôt les résultats :
fromRoman should only accept uppercase input ... ok toRoman should always return uppercase ... ok fromRoman should fail with malformed antecedents ... ok fromRoman should fail with repeated pairs of numerals ... ok fromRoman should fail with too many repeated numerals ... ok fromRoman should give known result with known input ... ok toRoman should give known result with known input ... ok fromRoman(toRoman(n))==n for all n ... ok toRoman should fail with non-integer input ... ok toRoman should fail with negative input ... ok toRoman should fail with large input ... ok toRoman should fail with 0 input ... ok ---------------------------------------------------------------------- Ran 12 tests in 2.864s OK
Quand tous vos tests passent, arrêtez d’écrire du code. |
<< roman.py, étape 4 |
| 1 | 2 | 3 | 4 | 5 | |
Refactorisation >> |