您现在的位置: 首页 > 深入 Python > 性能调优 > 优化正则表达式 | << >> | ||||
深入 Python从 Python 新手到专家 |
Soundex 函数首先检查输入是否是一个非空的字母字符串。 最好的方法是什么?
如果你回答“正则表达式”,去角落里反省一下你糟糕的直觉吧。 正则表达式几乎从来都不是正确的答案; 应该尽可能避免使用它们。 不仅仅是出于性能原因,还因为它们难以调试和维护。 当然,还有性能原因。
这段来自 soundex/stage1/soundex1a.py 的代码片段检查函数参数 source 是否是一个完全由字母组成的单词,并且至少包含一个字母(不是空字符串)
allChars = string.uppercase + string.lowercase if not re.search('^[%s]+$' % allChars, source): return "0000"
soundex1a.py 的性能如何? 为了方便起见,脚本的 __main__ 部分包含以下代码,该代码调用 timeit 模块,使用三个不同的名称设置计时测试,对每个名称测试三次,并显示每个名称的最小时间
if __name__ == '__main__': from timeit import Timer names = ('Woo', 'Pilgrim', 'Flingjingwaller') for name in names: statement = "soundex('%s')" % name t = Timer(statement, "from __main__ import soundex") print name.ljust(15), soundex(name), min(t.repeat())
那么使用此正则表达式时, soundex1a.py 的性能如何?
C:\samples\soundex\stage1>python soundex1a.py Woo W000 19.3356647283 Pilgrim P426 24.0772053431 Flingjingwaller F452 35.0463220884
正如你所料,当使用较长的名称调用时,算法需要更长的时间。 我们可以做一些事情来缩小这种差距(使函数在处理较长输入时花费更少的相对时间),但算法的性质决定了它永远不会在恒定时间内运行。
另一件要记住的事情是,我们正在测试一个具有代表性的姓名样本。 Woo 是一种琐碎的情况,因为它被缩短为一个字母,然后用零填充。 Pilgrim 是一种正常情况,长度平均,并且包含重要字母和被忽略字母的混合。 Flingjingwaller 非常长,并且包含连续的重复字符。 其他测试可能也有帮助,但这涵盖了各种不同的情况。
那么那个正则表达式呢? 好吧,效率很低。 由于表达式正在测试字符范围(大写的 A-Z 和小写的 a-z),因此我们可以使用简写的正则表达式语法。 这是 soundex/stage1/soundex1b.py
if not re.search('^[A-Za-z]+$', source): return "0000"
timeit 表示 soundex1b.py 比 soundex1a.py 略快,但没什么可兴奋的
C:\samples\soundex\stage1>python soundex1b.py Woo W000 17.1361133887 Pilgrim P426 21.8201693232 Flingjingwaller F452 32.7262294509
我们在 第 15.3 节“重构” 中看到,正则表达式可以被编译和重用以获得更快的结果。 由于此正则表达式在函数调用中永远不会改变,因此我们可以编译一次并使用编译后的版本。 这是 soundex/stage1/soundex1c.py
isOnlyChars = re.compile('^[A-Za-z]+$').search def soundex(source): if not isOnlyChars(source): return "0000"
在 soundex1c.py 中使用编译后的正则表达式要快得多
C:\samples\soundex\stage1>python soundex1c.py Woo W000 14.5348347346 Pilgrim P426 19.2784703084 Flingjingwaller F452 30.0893873383
但这方法错了吗? 这里的逻辑很简单:输入 source 不能为空,并且必须完全由字母组成。 编写一个循环来检查每个字符,并完全不用正则表达式,会不会更快?
这是 soundex/stage1/soundex1d.py
if not source: return "0000" for c in source: if not ('A' <= c <= 'Z') and not ('a' <= c <= 'z'): return "0000"
事实证明, soundex1d.py 中的这种技术 并不 比使用编译后的正则表达式快(尽管它比使用未编译的正则表达式快)
C:\samples\soundex\stage1>python soundex1d.py Woo W000 15.4065058548 Pilgrim P426 22.2753567842 Flingjingwaller F452 37.5845122774
为什么 soundex1d.py 不更快? 答案在于 Python 的解释性质。 正则表达式引擎是用 C 语言编写的,并编译为在本机计算机上运行。 另一方面,此循环是用 Python 编写的,并通过 Python 解释器运行。 即使循环相对简单,但它也不足以弥补被解释的开销。 正则表达式从来都不是正确的答案……除非它们是。
事实证明, Python 提供了一个鲜为人知的字符串方法。 你可以原谅不知道它,因为它在这本书中从未被提及过。 该方法称为 isalpha(),它检查字符串是否仅包含字母。
这是 soundex/stage1/soundex1e.py
if (not source) or (not source.isalpha()): return "0000"
我们在 soundex1e.py 中使用这种特定方法获得了多少收益? 相当多。
C:\samples\soundex\stage1>python soundex1e.py Woo W000 13.5069504644 Pilgrim P426 18.2199394057 Flingjingwaller F452 28.9975225902
import string, re charToSoundex = {"A": "9", "B": "1", "C": "2", "D": "3", "E": "9", "F": "1", "G": "2", "H": "9", "I": "9", "J": "2", "K": "2", "L": "4", "M": "5", "N": "5", "O": "9", "P": "1", "Q": "2", "R": "6", "S": "2", "T": "3", "U": "9", "V": "1", "W": "9", "X": "2", "Y": "9", "Z": "2"} def soundex(source): if (not source) or (not source.isalpha()): return "0000" source = source[0].upper() + source[1:] digits = source[0] for s in source[1:]: s = s.upper() digits += charToSoundex[s] digits2 = digits[0] for d in digits[1:]: if digits2[-1] != d: digits2 += d digits3 = re.sub('9', '', digits2) while len(digits3) < 4: digits3 += "0" return digits3[:4] if __name__ == '__main__': from timeit import Timer names = ('Woo', 'Pilgrim', 'Flingjingwaller') for name in names: statement = "soundex('%s')" % name t = Timer(statement, "from __main__ import soundex") print name.ljust(15), soundex(name), min(t.repeat())
<< 使用 timeit 模块 |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
优化字典查找 >> |