你现在的位置: 首页 > 深入 Python > 重构 > 重构 | << >> | ||||
深入 Python从 Python 新手到专家 |
全面的单元测试最大的好处不是当你所有的测试用例最终都通过时的那种感觉,甚至也不是当别人指责你破坏了他们的代码而你实际上可以证明你没有做的时候的那种感觉。单元测试最大的好处是,它给了你无情重构的自由。
重构是指在不改变代码功能的情况下,对其进行改进的过程。通常,“更好”意味着“更快”,尽管它也可以意味着“使用更少的内存”,或“使用更少的磁盘空间”,或者仅仅是“更优雅”。无论对你、对你的项目、在你的环境中意味着什么,重构对任何程序的长期健康发展都很重要。
在这里,“更好”意味着“更快”。具体来说,fromRoman 函数比它需要的速度慢,因为它使用了那个又大又笨重的正则表达式来验证罗马数字。试图完全抛弃正则表达式可能不值得(这将是困难的,而且最终可能不会更快),但你可以通过预编译正则表达式来加快函数的速度。
>>> 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>
![]() |
|
每当你打算多次使用一个正则表达式时,你都应该编译它以获得一个模式对象,然后直接调用该模式对象上的方法。 |
此文件位于示例目录的 py/roman/stage8/ 中。
如果你还没有这样做,你可以 下载这个和本书中使用的其他示例。
# 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
那么编译正则表达式到底能快多少呢?你自己看看
.............---------------------------------------------------------------------- Ran 13 tests in 3.385s
OK
![]() |
这里顺便说一句:这一次,我运行单元测试时没有 使用 -v 选项,所以你不会看到每个测试的完整 文档字符串,而只是看到每个通过的测试都有一个点。(如果一个测试失败,你会看到一个 F,如果它有一个错误,你会看到一个 E。你仍然会看到每个失败和错误的完整回溯,所以你可以追踪任何问题。) |
![]() |
你在 3.385 秒内运行了 13 个测试,相比之下,没有预编译正则表达式需要 3.685 秒。总体上提高了 8%,请记住,单元测试期间花费的大部分时间都花在了其他事情上。(另外,我单独对正则表达式进行了时间测试,不包括其他单元测试,发现编译此正则表达式平均可以使 search 的速度提高 54%。)对于这样一个简单的修复来说,这已经很不错了。 |
![]() |
哦,如果你想知道的话,预编译正则表达式并没有破坏任何东西,你刚刚证明了这一点。 |
我还想尝试另一种性能优化。考虑到正则表达式语法的复杂性,写同一个表达式经常有多种方法也就不足为奇了。在 comp.lang.python 上对这个模块进行了一些讨论之后,有人建议我尝试对可选的重复字符使用 {m,n} 语法。
此文件位于示例目录的 py/roman/stage8/ 中。
如果你还没有这样做,你可以 下载这个和本书中使用的其他示例。
# 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})$')![]()
这种形式的正则表达式稍微短一些(尽管可读性没有提高)。最大的问题是,它是否更快?
............. ---------------------------------------------------------------------- Ran 13 tests in 3.315sOK
我还想做另一个调整,然后我保证我会停止重构,并把这个模块放到一边。正如你反复看到的那样,正则表达式会很快变得非常复杂和难以阅读。我不希望六个月后回到这个模块,并试图维护它。当然,测试用例通过了,所以我知道它可以工作,但如果我无法弄清楚它是如何工作的,那么添加新功能、修复新错误或以其他方式维护它仍然很困难。正如你在 第 7.5 节“详细的正则表达式” 中看到的那样,Python 提供了一种逐行记录逻辑的方法。
此文件位于示例目录的 py/roman/stage8/ 中。
如果你还没有这样做,你可以 下载这个和本书中使用的其他示例。
# 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)![]()
<< 处理不断变化的需求 |
| 1 | 2 | 3 | 4 | 5 | |
附录 >> |