| You are here: Inicio > Inmersión en Python > Refactorización > Refactorización | << >> | ||||
Inmersión en PythonPython de novato a experto |
|||||
Lo mejor de las pruebas unitarias exhaustivas no es la sensación que le queda cuando todos los casos de prueba terminan por pasar, o incluso la que le llega cuando alguien le acusa de romper su código y usted puede probar realmente que no lo hizo. Lo mejor de las pruebas unitarias es que le da la libertad de refactorizar sin piedad.
La refactorización es el proceso de tomar código que funciona y hacer que funcione mejor. Normalmente “mejor” significa “más rápido”, aunque puede significar también que “usa menos memoria” o “usa menos espacio en disco” o simplemente “es más elegante”. Independientemente de lo que signifique para usted, su proyecto, en su entorno, la refactorización es importante para la salud a largo plazo de cualquier programa.
Aquí, “mejor” significa “más rápido”. Específicamente, la función fromRoman es más lenta de lo que podría ser debido a esa expresión regular tan grande y fea que usamos para validar los números romanos. Probablemente no merece la pena eliminar completamente las expresiones regulares (sería difícil, y podría acabar por no ser más rápido), pero podemos acelerar la función compilando la expresión regular previamente.
>>> 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>
| Siempre que vaya a usar una expresión regular más de una vez, debería compilarla para obtener un objeto patrón y luego llamar directamente a los métodos del patrón. | |
Este fichero está disponible en py/roman/stage8/ dentro del directorio de ejemplos.
Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este libro.
# 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
¿Cuánto más rápido es compilar las expresiones regulares? Véalo por sí mismo:
.............---------------------------------------------------------------------- Ran 13 tests in 3.385s
OK
| Sólo una nota al respecto: esta vez he ejecutado la prueba unitaria sin la opción -v, así que en lugar de la cadena de documentación completa por cada prueba lo que obtenemos es un punto cada vez que pasa una. (Si una prueba falla obtendremos una F y si tiene un error veremos una E. Seguimos obteniendo volcados de pila completos por cada fallo y error, para poder encontrar cualquier problema). | |
| Ejecutamos 13 pruebas en 3.385 segundos, comparado con los 3.685 segundos sin precompilar las expresiones regulares. Eso es una mejora global del 8%, y recuerde que la mayoría del tiempo de la prueba se pasa haciendo otras cosa. (He probado separadamente las expresiones regulares por sí mismas, aparte del resto de pruebas unitarias, y encontré que compilar esta expresión regular acelera la búsqueda -search- una media del 54%). No está mal para una simple corrección. | |
| Oh, y en caso de que se lo esté preguntando, precompilar la expresión regular no rompió nada como acabo de probar. |
Hay otra optimización de rendimiento que me gustaría probar. Dada la complejidad de la sintaxis de las expresiones regulares, no debería sorprenderle que con frecuencia haya más de una manera de escribir la misma expresión. Tras discutir un poco este módulo en comp.lang.python alguien me sugirió que probase la sintaxis {m,n} con los caracteres repetidos opcionales.
Este fichero está disponible en py/roman/stage8/ dentro del directorio de ejemplos.
Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este libro.
# se omite el resto del programa por claridad #versión antigua #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?)$') #versión nueva romanNumeralPattern = \ re.compile('^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$')![]()
Esta forma de expresión regular es un poco más corta (aunque no más legible). La gran pregunta es, ¿es más rápida?
............. ---------------------------------------------------------------------- Ran 13 tests in 3.315sOK
Me gustaría hacer otra mejora más y entonces prometo que pararé de refactorizar y dejaré este módulo. Como ha visto repetidas veces, las expresiones regulares pueden volverse rápidamente bastante incomprensibles e ilegibles. No me gustaría volver sobre este módulo en seis meses y tener que mantenerlo. Por supuesto, los casos de prueba pasan todos así que sé que funciona, pero si no puedo imaginar cómo funciona va a ser difícil añadir nuevas características, arreglar nuevos fallos o simplemente mantenerlo. Como vio en Sección 7.5, “Expresiones regulares prolijas”, Python proporciona una manera de documentar la lógica línea por línea.
Este fichero está disponible en py/roman/stage8/ dentro del directorio de ejemplos.
Si aún no lo ha hecho, puede descargar éste ejemplo y otros usados en este libro.
# se omite el resto del programa por claridad #versión antigua #romanNumeralPattern = \ # re.compile('^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$') #versión nueva 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.315sOK
<< Tratamiento del cambio de requisitos |
| 1 | 2 | 3 | 4 | 5 | |
Epílogo >> |