第 18 章 性能调优

性能调优是一件充满魅力的事情。尽管 Python 是一种解释型语言,但这并不意味着您不应该关注代码优化。但也不要过分担心。

18.1. 深入探讨

优化代码的过程中存在许多陷阱,很难知道从哪里开始。

让我们从这里开始:您确定需要这样做吗? 您的代码真的那么糟糕吗?值得花时间去优化它吗?在应用程序的整个生命周期中,与等待远程数据库服务器或等待用户输入相比,运行该代码将花费多少时间?

其次,您确定已经完成编码了吗? 过早优化就像在半生不熟的蛋糕上涂糖霜。您花费数小时或数天(甚至更多)来优化代码以提高性能,却发现它无法完成您需要它做的事情。这纯粹是浪费时间。

这并不是说代码优化毫无价值,而是您需要查看整个系统并决定这是否是您时间的最佳利用方式。您花在优化代码上的每一分钟,都是您没有花在添加新功能、编写文档、陪伴孩子或编写单元测试上的时间。

对了,单元测试。不用说,在开始性能调优之前,您需要一套完整的单元测试。您最不需要的就是在摆弄算法时引入新的错误。

有了这些注意事项,让我们来看看一些优化 Python 代码的技术。这里讨论的代码是 Soundex 算法的实现。Soundex 是 20 世纪初美国人口普查中用于对姓氏进行分类的一种方法。它将发音相似的名字归类在一起,因此即使名字拼写错误,研究人员也有机会找到它。Soundex 今天仍在使用,原因大致相同,当然我们现在使用的是计算机化的数据库服务器。大多数数据库服务器都包含 Soundex 函数。

Soundex 算法有几种细微的变化。本章使用的是这种

  1. 保留名称的第一个字母。
  2. 根据特定表将剩余字母转换为数字
    • B、F、P 和 V 变为 1。
    • C、G、J、K、Q、S、X 和 Z 变为 2。
    • D 和 T 变为 3。
    • L 变为 4。
    • M 和 N 变为 5。
    • R 变为 6。
    • 所有其他字母变为 9。
  3. 删除连续重复项。
  4. 完全删除所有 9。
  5. 如果结果短于四个字符(第一个字母加上三位数字),则在结果后面填充尾随零。
  6. 如果结果长于四个字符,则丢弃第四个字符之后的所有内容。

例如,我的名字 Pilgrim 变为 P942695。它没有连续重复项,因此无需进行任何操作。然后删除 9,剩下 P4265。这太长了,因此丢弃多余的字符,剩下 P426。

另一个例子:Woo 变为 W99,然后变为 W9,再变为 W,最后用零填充变为 W000。

以下是 Soundex 函数的第一次尝试

示例 18.1. soundex/stage1/soundex1a.py

如果您还没有这样做,您可以 下载本书中使用的这个和其他示例


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):
    "convert string to Soundex equivalent"

    # Soundex requirements:
    # source string must be at least 1 character
    # and must consist entirely of letters
    allChars = string.uppercase + string.lowercase
    if not re.search('^[%s]+$' % allChars, source):
        return "0000"

    # Soundex algorithm:
    # 1. make first character uppercase
    source = source[0].upper() + source[1:]
    
    # 2. translate all other characters to Soundex digits
    digits = source[0]
    for s in source[1:]:
        s = s.upper()
        digits += charToSoundex[s]

    # 3. remove consecutive duplicates
    digits2 = digits[0]
    for d in digits[1:]:
        if digits2[-1] != d:
            digits2 += d
        
    # 4. remove all "9"s
    digits3 = re.sub('9', '', digits2)
    
    # 5. pad end with "0"s to 4 characters
    while len(digits3) < 4:
        digits3 += "0"
        
    # 6. return first 4 characters
    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())

Soundex 的进一步阅读