Dive-Into-Python-中文版-六-

51 阅读1小时+

Dive Into Python 中文版(六)

第十八章 性能优化

第十八章 性能优化

  • 18.1. 概览
  • 18.2. 使用 timeit 模块
  • 18.3. 优化正则表达式
  • 18.4. 优化字典查找
  • 18.5. 优化列表操作
  • 18.6. 优化字符串操作
  • 18.7. 小结

性能优化 (Performance tuning) 是一件多姿多彩的事情。Python 是一种解释性语言并不表示你不应该担心代码优化。但也不必 担心。

18.1. 概览

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()) 

进一步阅读

  • Soundexing and Genealogy 给出了 Soundex 发展的年代表以及地域变化。

18.2. 使用 timeit 模块

18.2. 使用 timeit 模块

关于 Python 代码优化你需要知道的最重要问题是,决不要自己编写计时函数。

为一个很短的代码计时都很复杂。处理器有多少时间用于运行这个代码?有什么在后台运行吗?每个现代计算机都在后台运行持续或者间歇的程序。小小的疏忽可能破坏你的百年大计,后台服务偶尔被 “唤醒” 在最后千分之一秒做一些像查收信件,连接计时通信服务器,检查应用程序更新,扫描病毒,查看是否有磁盘被插入光驱之类很有意义的事。在开始计时测试之前,把一切都关掉,断开网络的连接。再次确定一切都关上后关掉那些不断查看网络是否恢复的服务等等。

接下来是计时框架本身引入的变化因素。Python 解释器是否缓存了方法名的查找?是否缓存代码块的编译结果?正则表达式呢? 你的代码重复运行时有副作用吗?不要忘记,你的工作结果将以比秒更小的单位呈现,你的计时框架中的小错误将会带来不可挽回的结果扭曲。

Python 社区有句俗语:“Python 自己带着电池。” 别自己写计时框架。Python 2.3 具备一个叫做 timeit 的完美计时工具。

例 18.2. timeit 介绍

如果您还没有下载本书附带的样例程序, 可以 下载本程序和其他样例程序

>>> import timeit
>>> t = timeit.Timer("soundex.soundex('Pilgrim')",
... "import soundex")   
>>> t.timeit()              
8.21683733547
>>> t.repeat(3, 2000000)    
[16.48319309109, 16.46128984923, 16.44203948912] 
[1]timeit 模块定义了接受两个参数的 Timer 类。两个参数都是字符串。第一个参数是你要计时的语句,这里你计时的是以'Pilgrim'参数调用 Soundex 函数。传递给 Timer 的第二个参数是为第一个参数语句构建环境的导入语句。从内部讲,timeit 构建起一个独立的虚拟环境,手工地执行建立语句 (导入 soundex 模块),然后手工地编译和执行被计时语句 (调用 Soundex 函数)。
[2]只要有了 Timer 对象,最简单的事就是调用 timeit(),它调用你的函数一百万次并返回所耗费的秒数。
[3]Timer 对象的另一个主要方法是 repeat(),它接受两个可选参数。第一个参数是重复整个测试的次数,第二个参数是每个测试中调用被计时语句的次数。两个参数都是可选的,它们的默认值分别是 31000000repeat() 方法返回以秒记录的每个测试循环的耗时列表。

提示 你可以在命令行使用 timeit 模块来测试一个已存在的 Python 程序,而不需要修改代码。在 docs.python.org/lib/node396.html 查看文档中关于命令行选项的内容。

注意 repeat() 返回一个时间列表。由于 Python 计时器使用的处理器时间的微小变化 (或者那些你没办法根除的可恶的后台进程),这些时间中几乎不可能出现重复。你的第一想法也许是说:“让我们求平均值获得真实的数据。”

事实上,那几乎是确定错误的。你的代码或者 Python 解释器的变化可能缩短耗时,那些没办法去除的可恶后台进程或者其他 Python 解释器以外的因素也许令耗时延长。如果计时结果之间的差异超过百分之几,太多的可变因素使你没法相信结果,如果不是这样则可以取最小值而丢弃其他结果。

Python 有一个方便的 min 函数返回输入列表中的最小值:

>>> min(t.repeat(3, 1000000))
8.22203948912 

提示 timeit 模块只有在你知道哪段代码需要优化时使用。如果你有一个很大的 Python 程序并且不知道你的性能问题所在,查看 hotshot 模块

18.3. 优化正则表达式

18.3. 优化正则表达式

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.pysoundex1a.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 中恰好不及 编译后的正则表达式快 (尽管比使用未编译的正则表达式快[14]):

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) and (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 

例 18.3. 目前为止最好的结果:soundex/stage1/soundex1e.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):
    if (not source) and (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()) 

Footnotes

[14] 注意 soundex1d.py 在后两个测试点上都比 soundex1b.py 慢,这点与作者所说的矛盾。本章另还有多处出现了正文与测试结果矛盾的地方,每个地方都会用译注加以说明。这个 bug 将在下个版本中得到修正。――译注

18.4. 优化字典查找

18.4. 优化字典查找

Soundex 算法的第二步是依照特定规则将字符转换为数字。做到这点最好的方法是什么?

最明显的解决方案是定义一个以单字符为键并以所对应数字为值的字典,以字典查找每个字符。这便是 soundex/stage1/soundex1e.py 中使用的方法 (目前最好的结果):

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):
    # ... input check omitted for brevity ...
    source = source[0].upper() + source[1:]
    digits = source[0]
    for s in source[1:]:
        s = s.upper()
        digits += charToSoundex[s] 

你已经为 soundex1e.py 计时,这便是其表现:

C:\samples\soundex\stage1>python soundex1c.py
Woo             W000 13.5069504644
Pilgrim         P426 18.2199394057
Flingjingwaller F452 28.9975225902 

这段代码很直接,但它是最佳解决方案吗?为每个字符分别调用 upper() 看起来不是很有效率,为整个字符串调用 upper() 一次可能会好些。

然后是一砖一瓦地建立 digits 字符串。一砖一瓦的建造好像非常欠缺效率。在 Python 内部,解释器需要在循环的每一轮创建一个新的字符串,然后丢弃旧的。

但是,Python 擅长于列表。可以自动地将字符串作为列表来对待。而且使用 join() 方法可以很容易地将列表合并成字符串。

这便是 soundex/stage2/soundex2a.py,通过 maplambda 把所有字母转换为数字:

 def soundex(source):
    # ...
    source = source.upper()
    digits = source[0] + "".join(map(lambda c: charToSoundex[c], source[1:])) 

太震惊了,soundex2a.py 并不快:

C:\samples\soundex\stage2>python soundex2a.py
Woo             W000 15.0097526362
Pilgrim         P426 19.254806407
Flingjingwaller F452 29.3790847719 

匿名 lambda 函数的使用耗费掉了从以字符列表替代字符串争取来的时间。

soundex/stage2/soundex2b.py 使用了一个列表遍历来替代 maplambda

 source = source.upper()
    digits = source[0] + "".join([charToSoundex[c] for c in source[1:]]) 

soundex2b.py 中使用列表遍历比 soundex2a.py 中使用 maplambda 快,但还没有最初的代码快 (soundex1e.py 中一砖一瓦的构建字符串[15]):

C:\samples\soundex\stage2>python soundex2b.py
Woo             W000 13.4221324219
Pilgrim         P426 16.4901234654
Flingjingwaller F452 25.8186157738 

是时候从本质不同的方法来思考了。字典查找是一个普通目的实现工具。字典的键可以是任意长度的字符串 (或者很多其他数据类型) 但这里我们只和单字符键 单字符值打交道。恰巧 Python 有处理这种情况的特别函数:string.maketrans 函数。

这便是 soundex/stage2/soundex2c.py

allChar = string.uppercase + string.lowercase
charToSoundex = string.maketrans(allChar, "91239129922455912623919292" * 2)
def soundex(source):
    # ...
    digits = source[0].upper() + source[1:].translate(charToSoundex) 

这儿在干什么?string.maketrans 创建一个两个字符串间的翻译矩阵:第一参数和第二参数。就此而言,第一个参数是字符串 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,第二个参数是字符串 9123912992245591262391929291239129922455912623919292。看到其模式了?恰好与我们用冗长的字典构建的模式相同。A 映射到 9,B 映射到 1,C 映射到 2 等等。但它不是一个字典。而是一个你可以通过字符串方法 translate 使用的特别数据结构。它根据 string.maketrans 定义的矩阵将每个字符翻译为对应的数字。

timeit 显示 soundex2c.py 比定义字典并对输入进行循环一砖一瓦地构建输出快很多:

C:\samples\soundex\stage2>python soundex2c.py
Woo             W000 11.437645008
Pilgrim         P426 13.2825062962
Flingjingwaller F452 18.5570110168 

你不可能做得更多了。Python 有一个特殊函数,通过使用它做到了一个和你的工作差不多的事情。就用它并继续吧!

例 18.4. 目前的最佳结果:soundex/stage2/soundex2c.py

 import string, re
allChar = string.uppercase + string.lowercase
charToSoundex = string.maketrans(allChar, "91239129922455912623919292" * 2)
def soundex(source):
    if (not source) or (not source.isalpha()):
        return "0000"
    digits = source[0].upper() + source[1:].translate(charToSoundex)
    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()) 

Footnotes

[15] 事实恰好相反,soundex2b.py 在每个点上都快于 soundex1e.py。――译注

18.5. 优化列表操作

18.5. 优化列表操作

Soundex 算法的第三步是去除连续重复字符。怎样做是最佳方法?

这里是我们目前在 soundex/stage2/soundex2c.py 中的代码:

 digits2 = digits[0]
    for d in digits[1:]:
        if digits2[-1] != d:
            digits2 += d 

这里是 soundex2c.py 的性能表现:

C:\samples\soundex\stage2>python soundex2c.py
Woo             W000 11.437645008
Pilgrim         P426 13.2825062962
Flingjingwaller F452 18.5570110168 

第一件事是考虑,考察在循环的每一轮都检查 digits[-1] 是否有效率。列表索引代价大吗?如果把上一个数字存在另外的变量中以便检查是否会获益?

这里的 soundex/stage3/soundex3a.py 将回答这个问题:

 digits2 = ''
    last_digit = ''
    for d in digits:
        if d != last_digit:
            digits2 += d
            last_digit = d 

soundex3a.py 并不比 soundex2c.py 运行得快多少,而且甚至可能更会慢些 (差异还没有大到可以确信这一点):

C:\samples\soundex\stage3>python soundex3a.py
Woo             W000 11.5346048171
Pilgrim         P426 13.3950636184
Flingjingwaller F452 18.6108927252 

为什么 soundex3a.py 不更快呢?其实 Python 的索引功能恰恰很有效。重复使用 digits2[-1] 根本没什么问题。另一方面,手工保留上一个数字意味着我们每存储一个数字都要为两个 变量赋值,这便抹杀了我们避开索引查找所带来的微小好处。

让我们从本质上不同的方法来思考。如果可以把字符串当作字符列表来对待,那么使用列表遍历遍寻列表便成为可能。问题是代码需要使用列表中的上一个字符,而且使用列表遍历做到这一点并不容易。

但是,使用内建的 range() 函数创建一个索引数字构成的列表是可以的。使用这些索引数字一步步搜索列表并拿出与前面不同的字符。这样将使你得到一个字符串列表,使用字符串方法 join() 便可重建字符串。

这便是 soundex/stage3/soundex3b.py

 digits2 = "".join([digits[i] for i in range(len(digits))
                       if i == 0 or digits[i-1] != digits[i]]) 

这样快了吗?一个字,否。

C:\samples\soundex\stage3>python soundex3b.py
Woo             W000 14.2245271396
Pilgrim         P426 17.8337165757
Flingjingwaller F452 25.9954005327 

有可能因为目前的这些方法都是 “字符串中心化” 的。Python 可以通过一个命令把一个字符串转化为一个字符列表:list('abc') 返回 ['a', 'b', 'c']。更进一步,列表可以被很快地就地 改变。与其一砖一瓦地建造一个新的列表 (或者字符串),为什么不选择操作列表的元素呢?

这便是 soundex/stage3/soundex3c.py,就地修改列表去除连续重复元素:

 digits = list(source[0].upper() + source[1:].translate(charToSoundex))
    i=0
    for item in digits:
        if item==digits[i]: continue
        i+=1
        digits[i]=item
    del digits[i+1:]
    digits2 = "".join(digits) 

这比 soundex3a.pysoundex3b.py 快吗?不,实际上这是目前最慢的一种方法[16]:

C:\samples\soundex\stage3>python soundex3c.py
Woo             W000 14.1662554878
Pilgrim         P426 16.0397885765
Flingjingwaller F452 22.1789341942 

我们在这儿除了试用了几种 “聪明” 的技术,根本没有什么进步。到目前为止最快的方法就是最直接的原始方法 (soundex2c.py)。有时候聪明未必有回报。

例 18.5. 目前的最佳结果:soundex/stage2/soundex2c.py

 import string, re
allChar = string.uppercase + string.lowercase
charToSoundex = string.maketrans(allChar, "91239129922455912623919292" * 2)
def soundex(source):
    if (not source) or (not source.isalpha()):
        return "0000"
    digits = source[0].upper() + source[1:].translate(charToSoundex)
    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()) 

Footnotes

[16] soundex3c.pysoundex3b.py 快。――译注

18.6. 优化字符串操作

18.6. 优化字符串操作

Soundex 算法的最后一步是对短结果补零和截短长结果。最佳的做法是什么?

这是目前在 soundex/stage2/soundex2c.py 中的做法:

 digits3 = re.sub('9', '', digits2)
    while len(digits3) < 4:
        digits3 += "0"
    return digits3[:4] 

这里是 soundex2c.py 的表现:

C:\samples\soundex\stage2>python soundex2c.py
Woo             W000 12.6070768771
Pilgrim         P426 14.4033353401
Flingjingwaller F452 19.7774882003 

思考的第一件事是以循环取代正则表达式。这里的代码来自 soundex/stage4/soundex4a.py

 digits3 = ''
    for d in digits2:
        if d != '9':
            digits3 += d 

soundex4a.py 快了吗?是的:

C:\samples\soundex\stage4>python soundex4a.py
Woo             W000 6.62865531792
Pilgrim         P426 9.02247576158
Flingjingwaller F452 13.6328416042 

但是,等一下。一个从字符串去除字符的循环?我们可以用一个简单的字符串方法做到。这便是 soundex/stage4/soundex4b.py

 digits3 = digits2.replace('9', '') 

soundex4b.py 快了吗?这是个有趣的问题,它取决输入值:

C:\samples\soundex\stage4>python soundex4b.py
Woo             W000 6.75477414029
Pilgrim         P426 7.56652144337
Flingjingwaller F452 10.8727729362 

soundex4b.py 中的字符串方法对于大多数名字比循环快,但是对于短小的情况 (很短的名字) 却比 soundex4a.py 略微慢些。性能优化并不总是一致的,对于一个情况快些,却可能对另外一些情况慢些。就此而言,大多数情况将会从改变中获益,所以就改吧,但是别忘了原则。

最后仍很重要的是,让我们检测算法的最后两步:以零补齐短结果和截短超过四字符的长结果。你在 soundex4b.py 中看到的代码就是做这个工作的,但是太没效率了。看一下 soundex/stage4/soundex4c.py 找出原因:

 digits3 += '000'
    return digits3[:4] 

我们为什么需要一个 while 循环来补齐结果?我们早就知道我们需要把结果截成四字符,并且我们知道我们已经有了至少一个字符 (直接从 source 中拿过来的起始字符)。这意味着我们可以仅仅在输出的结尾添加三个零,然后截断它。不要害怕重新理解问题,从不太一样的角度看问题可以获得简单的解决方案。

我们丢弃 while 循环后从 soundex4c.py 中获得怎样的速度?太明显了:

C:\samples\soundex\stage4>python soundex4c.py
Woo             W000 4.89129791636
Pilgrim         P426 7.30642134685
Flingjingwaller F452 10.689832367 

最后,还有一件事可以令这三行运行得更快:你可以把它们合并为一行。看一眼 soundex/stage4/soundex4d.py

 return (digits2.replace('9', '') + '000')[:4] 

soundex4d.py 中把所有代码放在一行可以比 soundex4c.py 稍微快那么一点:

C:\samples\soundex\stage4>python soundex4d.py
Woo             W000 4.93624105857
Pilgrim         P426 7.19747593619
Flingjingwaller F452 10.5490700634 

它非常难懂,而且优化也不明显。这值得吗?我希望你有很好的见解。性能并不是一切。你在优化方面的努力应该与程序的可读性和可维护性相平衡。

18.7. 小结

18.7. 小结

这一章展示了性能优化的几个重要方面,这里是就 Python 而言,但它们却普遍适用。

  • 如果你要在正则表达式和编写循环间抉择,选择正则表达式。正则表达式因其是以 C 语言编译的可以本能地在你的计算机上运行,你的循环却以 Python 编写需要通过 Python 解释器运行。
  • 如果你需要在正则表达式和字符串方法间抉择,选择字符串方法。它们都是以 C 编译的,所以选取简单的。
  • 字典查找的通常应用很快,但是 string.maketrans 之类的特殊函数和 isalpha() 之类的字符串方法更快。如果 Python 有定制方法给你用,就使它吧!
  • 别太聪明了。有时一些明显的算法是最快的。
  • 不要太迷恋性能优化,性能并不是一切。

最后一点太重要了,这章中你令这个程序提速三倍并且令百万次的调用节省 20 秒。太棒了!现在思考一下:在那百万次的函数调用中,有多少秒花在周边应用程序等待数据库连接?花在磁盘输入/输出上?花在等待用户输入上?不要在过度优化算法上花时间,从而忽略了其它地方可以做的明显改进。开发你编写运行良好的 Python 代码的直觉,如果发现明显的失误则修正它们,并不对其它部分过分操作。

附录 A. 进一步阅读

第一章 安装 Python

第二章 第一个 Python 程序

第三章 内置数据类型

第四章 自省的威力

第五章 对象和面向对象

第六章 异常和文件处理

第七章 正则表达式

第八章 HTML 处理

第九章 XML 处理

  • 9.4. Unicode
    • Unicode.org 是 unicode 标准的主页,包含了一个简要的技术简介
    • Unicode 教程有更多关于如何使用 Python unicode 函数的例子,包括甚至在并不真的需要时如何将 unicode 强制转换为 ASCII。
    • PEP 263 涉及了何时、如何在你的.py文件中定义字符编码的更多细节。

第十章 脚本和流

第十一章 HTTP Web 服务

第十二章 SOAP Web 服务

第十三章 单元测试

第十四章 测试优先编程

第十五章 重构

第十六章 函数编程

第十七章 动态函数

第十八章 性能优化

附录 B. 五分钟回顾

附录 B. 五分钟回顾

第一章 安装 Python

  • 1.1. 哪一种 Python 适合您?

    学习 Python 的第一件事就是安装,不是吗?

  • 1.2. Windows 上的 Python

    在 Windows 上,安装 Python 有两种选择。

  • 1.3. Mac OS X 上的 Python

    在 Mac OS X 上,对于安装 Python 有两种选择:安装或不安装。您可能想要安装它。

  • 1.4. Mac OS 9 上的 Python

    Mac OS 9 上没有预装任何版本的 Python,安装相对简单,只有一种选择。

  • 1.5. RedHat Linux 上的 Python

    www.python.org/ftp/python/ 选择列出的最新的版本号, 然后选择 其中的rpms/ 目录下载最新的 Python RPM 包。 使用 rpm 命令进行安装,操作如下所示:

  • 1.6. Debian GNU/Linux 上的 Python

    如果您运行在 Debian GNU/Linux 上,安装 Python 需要使用 apt 命令。

  • 1.7. 从源代码安装 Python

    如果您宁愿从源码创建,可以从 www.python.org/ftp/python/下载 Python 的源代码。选择最新的版本,下载.tgz 文件,执行通常的 configure, make, make install 步骤。

  • 1.8. 使用 Python 的交互 Shell

    既然我们已经安装了 Python,那么我们运行的这个交互 shell 是什么东西呢?

  • 1.9. 小结

    您现在应该已经安装了一个可以工作的 Python 版本了。

第二章 第一个 Python 程序

  • 2.1. 概览

    这是一个完整的、可执行的 Python 程序。

  • 2.2. 函数声明

    与其它大多数语言一样 Python 有函数,但是它没有像 C++ 一样的独立的头文件;或者像 Pascal 一样的分离的 interface/implementation 段。在需要函数时,像下面这样声明即可:

  • 2.3. 文档化函数

    可以通过给出一个 doc string (文档字符串) 来文档化一个 Python 函数。

  • 2.4. 万物皆对象

    在 Python 中,函数同其它东西一样也是对象。

  • 2.5. 代码缩进

    Python 函数没有明显的 beginend,没有标明函数的开始和结束的花括号。唯一的分隔符是一个冒号 (:),接着代码本身是缩进的。

  • 2.6. 测试模块

    所有的 Python 模块都是对象,并且有几个有用的属性。您可以使用这些属性方便地测试您所编写的模块。下面是一个使用 if __name__ 的技巧。

第三章 内置数据类型

  • 3.1. Dictionary 介绍

    Dictionary 是 Python 的内置数据类型之一,它定义了键和值之间一对一的关系。

  • 3.2. List 介绍

    List 是 Python 中使用最频繁的数据类型。如果您对 list 仅有的经验就是在 Visual Basic 中的数组或 Powerbuilder 中的数据存储,那么就打起精神学习 Python 的 list 吧。

  • 3.3. Tuple 介绍

    Tuple 是不可变的 list。一旦创建了一个 tuple,就不能以任何方式改变它。

  • 3.4. 变量声明

    Python 与大多数其它语言一样有局部变量和全局变量之分,但是它没有明显的变量声明。变量通过首次赋值产生,当超出作用范围时自动消亡。

  • 3.5. 格式化字符串

    Python 支持格式化字符串的输出 。尽管这样可能会用到非常复杂的表达式,但最基本的用法是将一个值插入到一个有字符串格式符 %s 的字符串中。

  • 3.6. 映射 list

    Python 的强大特性之一是其对 list 的解析,它提供一种紧凑的方法,可以通过对 list 中的每个元素应用一个函数,从而将一个 list 映射为另一个 list。

  • 3.7. 连接 list 与分割字符串

    您有了一个形如 _key_=_value_ 的 key-value 对 list,并且想将它们合成为单个字符串。为了将任意包含字符串的 list 连接成单个字符串,可以使用字符串对象的 join 方法。

  • 3.8. 小结

    现在 odbchelper.py 程序和它的输出结果都应该非常清楚了。

第四章 自省的威力

  • 4.1. 概览

    下面是一个完整可运行的 Python 程序。大概看一下这段程序,你应该可以理解不少了。用数字标出的行阐述了 第二章 第一个 Python 程序 中涉及的一些概念。如果剩下来的代码看起来有点奇怪,不用担心,通过阅读本章你将会理解所有这些。

  • 4.2. 使用可选参数和命名参数

    Python 允许函数参数有缺省值;如果调用函数时不使用参数,参数将获得它的缺省值。此外,通过使用命名参数还可以以任意顺序指定参数。SQL Server Transact/SQL 中的存储过程也可以做到这些;如果你是脚本高手,你可以略过这部分。

  • 4.3. 使用 type、str、dir 和其它内置函数

    Python 有小部分相当有用的内置函数。除这些函数之外,其它所有的函数都被分到了各个模块中。其实这是一个非常明智的设计策略,避免了核心语言变得像其它脚本语言一样臃肿 (咳 咳,Visual Basic)。

  • 4.4. 通过 getattr 获取对象引用

    你已经知道 Python 函数是对象。你不知道的是,使用 getattr 函数,可以得到一个直到运行时才知道名称的函数的引用。

  • 4.5. 过滤列表

    如你所知,Python 具有通过列表解析 (第 3.6 节 “映射 list”) 将列表映射到其它列表的强大能力。这种能力同过滤机制结合使用,使列表中的有些元素被映射的同时跳过另外一些元素。

  • 4.6. and 和 or 的特殊性质

    在 Python 中,andor 执行布尔逻辑演算,如你所期待的一样。但是它们并不返回布尔值,而是返回它们实际进行比较的值之一。

  • 4.7. 使用 lambda 函数

    Python 支持一种有趣的语法,它允许你快速定义单行的最小函数。这些叫做 lambda 的函数,是从 Lisp 借用来的,可以用在任何需要函数的地方。

  • 4.8. 全部放在一起

    最后一行代码是唯一还没有解释过的,它完成全部的工作。但是现在工作已经简单了,因为所需要的每件事都已经按照需求建立好了。所有的多米诺骨牌已经就位,到了将它们推倒的时候了。

  • 4.9. 小结

    apihelper.py 程序和它的输出现在应该非常清晰了。

第五章 对象和面向对象

  • 5.1. 概览

    下面是一个完整的,可运行的 Python 程序。请阅读模块、类和函数的 doc strings,可以大概了解这个程序所做的事情和工作情况。像平时一样,不用担心你不理解的东西,这就是本章其它部分将告诉你的内容。

  • 5.2. 使用 from module import 导入模块

    Python 有两种导入模块的方法。两种都有用,你应该知道什么时候使用哪一种方法。一种方法,import _module_,你已经在第 2.4 节 “万物皆对象”看过了。另一种方法完成同样的事情,但是它与第一种有着细微但重要的区别。

  • 5.3. 类的定义

    Python 是完全面向对象的:你可以定义自已的类,从自已的或内置的类继承,然后从你定义的类创建实例。

  • 5.4. 类的实例化

    在 Python 中对类进行实例化很直接。要对类进行实例化,只要调用类 (就好像它是一个函数),传入定义在 __init__ 方法中的参数。返回值将是新创建的对象。

  • 5.5. 探索 UserDict:一个封装类

    如你所见,FileInfo 是一个有着像字典一样的行为方式的类。为了进一步揭示这一点,让我们看一看在 UserDict 模块中的 UserDict 类,它是我们的 FileInfo 类的父类。它没有什么特别的,也是用 Python 写的,并且保存在一个 .py 文件里,就像我们其他的代码。特别之处在于,它保存在你的 Python 安装目录的 lib 目录下。

  • 5.6. 专用类方法

    除了普通的类方法,Python 类还可以定义专用方法。专用方法是在特殊情况下或当使用特别语法时由 Python 替你调用的,而不是在代码中直接调用 (像普通的方法那样)。

  • 5.7. 高级专用类方法

    除了 __getitem____setitem__ 之外 Python 还有更多的专用函数。某些可以让你模拟出你甚至可能不知道的功能。

  • 5.8. 类属性介绍

    你已经知道了数据属性,它们是被一个特定的类实例所拥有的变量。Python 也支持类属性,它们是由类本身所拥有的。

  • 5.9. 私有函数

    与大多数的语言不同,一个 Python 函数,方法,或属性是私有还是公有,完全取决于它的名字。

  • 5.10. 小结

    实打实的对象把戏到此为止。你将在 第十二章 中看到一个真实世界应用程序的专有类方法,它使用 getattr 创建一个到远程 Web 服务的代理。

第六章 异常和文件处理

  • 6.1. 异常处理

    与许多面向对象语言一样,Python 具有异常处理,通过使用 try...except 块来实现。

  • 6.2. 与文件对象共事

    Python 有一个内置函数,open,用来打开在磁盘上的文件。open 返回一个文件对象,它拥有一些方法和属性,可以得到被打开文件的信息,以及对被打开文件进行操作。

  • 6.3. for 循环

    与其它大多数语言一样,Python 也拥有 for 循环。你到现在还未曾看到它们的唯一原因就是,Python 在其它太多的方面表现出色,通常你不需要它们。

  • 6.4. 使用 sys.modules

    与其它任何 Python 的东西一样,模块也是对象。只要导入了,总可以用全局 dictionary ``sys.modules 来得到一个模块的引用。

  • 6.5. 与目录共事

    os.path 模块有几个操作文件和目录的函数。这里,我们看看如何操作路径名和列出一个目录的内容。

  • 6.6. 全部放在一起

    再一次,所有的多米诺骨牌都放好了。我们已经看过每行代码是如何工作的了。现在往回走一步,看一下放在一起是怎么样的。

  • 6.7. 小结

    在 第五章 介绍的 fileinfo.py 程序现在应该完全理解了。

第七章 正则表达式

  • 7.1. 概览

    如果你要解决的问题利用字符串函数能够完成,你应该使用它们。它们快速、简单且容易阅读,而快速、简单、可读性强的代码可以说出很多好处。但是,如果你发现你使用了许多不同的字符串函数和 if 语句来处理一个特殊情况,或者你组合使用了 splitjoin 等函数而导致用一种奇怪的甚至读不下去的方式理解列表,此时,你也许需要转到正则表达式了。

  • 7.2. 个案研究:街道地址

    这一系列的例子是由我几年前日常工作中的现实问题启发而来的,当时我需要从一个老化系统中导出街道地址,在将它们导入新的系统之前,进行清理和标准化。(看,我不是只将这些东西堆到一起,它有实际的用处。)这个例子展示我如何处理这个问题。

  • 7.3. 个案研究:罗马字母

    你可能经常看到罗马数字,即使你没有意识到它们。你可能曾经在老电影或者电视中看到它们 (“版权所有 MCMXLVI” 而不是 “版权所有1946”),或者在某图书馆或某大学的贡献墙上看到它们 (“成立于 MDCCCLXXXVIII”而不是“成立于1888”)。你也可能在某些文献的大纲或者目录上看到它们。这是一个表示数字的系统,它实际上能够追溯到远古的罗马帝国 (因此而得名)。

  • 7.4. 使用 {n,m} 语法

    在前面的章节,你处理了相同字符可以重复三次的情况。在正则表达式中,有另外一个方式来表达这种情况,并且能提高代码的可读性。首先看看我们在前面的例子中使用的方法。

  • 7.5. 松散正则表达式

    迄今为止,你只是处理过被我称之为“紧凑”类型的正则表达式。正如你曾看到的,它们难以阅读,即使你清楚正则表达式的含义,你也不能保证六个月以后你还能理解它。你真正所需的就是利用内联文档 (inline documentation)。

  • 7.6. 个案研究:解析电话号码

    迄今为止,你主要是匹配整个模式,不论是匹配上,还是没有匹配上。但是正则表达式还有比这更为强大的功能。当一个模式确实 匹配上时,你可以获取模式中特定的片断,你可以发现具体匹配的位置。

  • 7.7. 小结

    这只是正则表达式能够完成工作的很少一部分。换句话说,即使你现在备受打击,相信我,你也不是什么也没见过了。

第八章 HTML 处理

  • 8.1. 概览

    我经常在 comp.lang.python 上看到关于如下的问题: “ 怎么才能从我的 HTML 文档中列出所有的 [头|图像|链接] 呢?” “怎么才能 [分析|解释|munge] 我的 HTML 文档的文本,但是又要保留标记呢?” “怎么才能一次给我所有的 HTML 标记 [增加|删除|加引号] 属性呢?” 本章将回答所有这些问题。

  • 8.2. sgmllib.py 介绍

    HTML 处理分成三步:将 HTML 分解成它的组成片段,对片段进行加工,接着将片段再重新合成 HTML。第一步是通过 sgmllib.py 来完成的,它是标准 Python 库的一部分。

  • 8.3. 从 HTML 文档中提取数据

    为了从 HTML 文档中提取数据,将 SGMLParser 类进行子类化,然后对想要捕捉的标记或实体定义方法。

  • 8.4. BaseHTMLProcessor.py 介绍

    SGMLParser 自身不会产生任何结果。它只是分析,分析,再分析,对于它找到的有趣的东西会调用相应的一个方法,但是这些方法什么都不做。SGMLParser 是一个 HTML 消费者 (consumer):它接收 HTML,将其分解成小的、结构化的小块。正如您所看到的,在前一节中,您可以定义 SGMLParser 的子类,它可以捕捉特别标记和生成有用的东西,如一个网页中所有链接的一个列表。现在我们将沿着这条路更深一步。我们要定义一个可以捕捉 SGMLParser 所丢出来的所有东西的一个类,接着重建整个 HTML 文档。用技术术语来说,这个类将是一个 HTML 生产者 (producer)

  • 8.5. locals 和 globals

    我们先偏离一下 HTML 处理的主题,讨论一下 Python 如何处理变量。Python 有两个内置的函数,localsglobals,它们提供了基于 dictionary 的访问局部和全局变量的方式。

  • 8.6. 基于 dictionary 的字符串格式化

    有另外一种字符串格式化的形式,它使用 dictionary 而不是值的 tuple。

  • 8.7. 给属性值加引号

    comp.lang.python 上的一个常见问题是 “我有一些 HTML 文档,属性值没有用引号括起来,并且我想将它们全部括起来,我怎么才能实现它呢?” [7] (一般这种事情的出现是由于一个项目经理加入到一个大的项目中来,而他又抱着 HTML 是一种标记语言的教条,要求所有的页面必须能够通过 HTML 校验器的验证。而属性值没有被引号括起来是一种常见的对 HTML 规范的违反。) 不管什么原因,未括起来的属性值通过将 HTML 送进 BaseHTMLProcessor 可以容易地修复。

  • 8.8. dialect.py 介绍

    DialectizerBaseHTMLProcessor 的简单 (和拙劣) 的派生类。它通过一系列的替换对文本块进行了处理,但是它确保在 &lt;pre&gt;...&lt;/pre&gt; 块之间的任何东西不被修改地通过。

  • 8.9. 全部放在一起

    到了将迄今为止我们已经学过并用得不错的东西放在一起的时候了。我希望您专心些。

  • 8.10. 小结

    Python 向您提供了一个强大工具,sgmllib.py,可以通过将 HTML 结构转变为一种对象模型来进行处理。可以以许多不同的方式来使用这个工具。

第九章 XML 处理

  • 9.1. 概览

    处理 XML 有两种基本的方式。一种叫做 SAX (“Simple API for XML”),它的工作方式是,一次读出一点 XML 内容,然后对发现的每一个元素调用一个方法。(如果你读了 第八章 HTML 处理,这应该听起来很熟悉,因为这是 sgmllib 工作的方式。) 另一种方式叫做 DOM (“Document Object Model”),它的工作方式是,一次性读入整个 XML 文档,然后使用 Python 类创建一个内部表示形式 (以树结构进行连接)。Python 拥有这两种解析方式的标准模块,但是本章只涉及 DOM。

  • 9.2. 包

    实际上解析一个 XML 文档是很简单的:只要一行代码。但是,在你接触那行代码前,需要暂时岔开一下,讨论一下包。

  • 9.3. XML 解析

    正如我说的,实际解析一个 XML 文档是非常简单的:只要一行代码。从这里出发到哪儿去就是你自己的事了。

  • 9.4. Unicode

    Unicode 是一个系统,用来表示世界上所有不同语言的字符。当 Python 解析一个 XML 文档时,所有的数据都是以 unicode 的形式保存在内存中的。

  • 9.5. 搜索元素

    通过一步步访问每一个节点的方式遍历 XML 文档可能很乏味。如果你正在寻找些特别的东西,又恰恰它们深深埋入了你的 XML 文档,有个捷径让你可以快速找到它:getElementsByTagName

  • 9.6. 访问元素属性

    XML 元素可以有一个或者多个属性,只要已经解析了一个 XML 文档,访问它们就太简单了。

  • 9.7. Segue

    以上就是 XML 的核心内容。下一章将使用相同的示例程序,但是焦点在于能使程序更加灵活的其它方面:使用输入流处理,使用 getattr 进行方法分发,并使用命令行标识允许用户重新配置程序而无需修改代码。

第十章 脚本和流

  • 10.1. 抽象输入源

    Python 的最强大力量之一是它的动态绑定,而动态绑定最强大的用法之一是类文件(file-like)对象

  • 10.2. 标准输入、输出和错误

    UNIX 用户已经对标准输入、标准输出和标准错误的概念非常熟悉了。这一节是为其他不熟悉的人准备的。

  • 10.3. 查询缓冲节点

    kgp.py 使用了多种技巧,在你进行 XML 处理时,它们或许能派上用场。第一个就是,利用输入文档的结构稳定特征来构建节点缓冲。

  • 10.4. 查找节点的直接子节点

    解析 XML 文档时,另一个有用的己技巧是查找某个特定元素的所有直接子元素。例如,在语法文件中,一个 ref 元素可以有数个 p 元素,其中每一个都可以包含很多东西,包括其他的 p 元素。你只要查找作为 ref 孩子的 p 元素,不用查找其他 p 元素的孩子 p 元素。

  • 10.5. 根据节点类型创建不同的处理器

    第三个有用的 XML 处理技巧是将你的代码基于节点类型和元素名称分散到逻辑函数中。解析后的 XML 文档是由各种类型的节点组成的,每一个都是通过 Python 对象表示的。文档本身的根层次通过一个 Document 对象表示。Document 还包含了一个或多个 Element 对象 (表示 XML 标记),其中的每一个可以包含其它的 Element 对象、Text 对象 (表示文本),或者 Comment 对象 (表示内嵌注释)。使用 Python 编写分离各个节点类型逻辑的分发器非常容易。

  • 10.6. 处理命令行参数

    Python 完全支持创建在命令行运行的程序,也支持通过命令行参数和短长样式来指定各种选项。这些并非是 XML 特定的,但是这样的脚本可以充分使用命令行处理,看来是时候提一下它了。

  • 10.7. 全部放在一起

    你已经了解很多基础的东西。让我们回来看看所有片段是如何整合到一起的。

  • 10.8. 小结

    Python 带有解析和操作 XML 文档非常强大的库。minidom 接收一个 XML 文件并将其解析为 Python 对象,并提供了对任意元素的随机访问。进一步,本章展示了如何利用 Python 创建一个“真实”独立的命令行脚本,连同命令行标志、命令行参数、错误处理,甚至从前一个程序的管道接收输入的能力。

第十一章 HTTP Web 服务

  • 11.1. 概览

    在讲解如何下载 web 页和如何从 URL 解析 XML 时,你已经学习了关于 HTML 处理和 XML 处理,接下来让我们来更全面地探讨有关 HTTP web 服务的主题。

  • 11.2. 避免通过 HTTP 重复地获取数据

    假如说你想用 HTTP 下载资源,例如一个 Atom feed 汇聚。你不仅仅想下载一次;而是想一次又一次地下载它,如每小时一次,从提供 news feed 的站点获得最新的消息。让我们首先用一种直接而原始的方法来实现它,然后看看如何改进它。

  • 11.3. HTTP 的特性

    这里有五个你必须关注的 HTTP 重要特性。

  • 11.4. 调试 HTTP web 服务

    首先,让我们开启 Python HTTP 库的调试特性并查看网络线路上的传输过程。这对本章的全部内容都很有用,因为你将添加越来越多的特性。

  • 11.5. 设置 User-Agent

    改善你的 HTTP web 服务客户端的第一步就是用 User-Agent 适当地鉴别你自己。为了做到这一点,你需要远离基本的 urllib 而深入到 urllib2

  • 11.6. 处理 Last-Modified 和 ETag

    既然你知道如何在你的 web 服务请求中添加自定义的 HTTP 头信息,接下来看看如何添加 Last-ModifiedETag 头信息的支持。

  • 11.7. 处理重定向

    你可以使用两种不同的自定义 URL 处理器来处理永久重定向和临时重定向。

  • 11.8. 处理压缩数据

    你要支持的最后一个重要的 HTTP 特性是压缩。许多 web 服务具有发送压缩数据的能力,这可以将网络线路上传输的大量数据消减 60% 以上。这尤其适用于 XML web 服务,因为 XML 数据 的压缩率可以很高。

  • 11.9. 全部放在一起

    你已经看到了构造一个智能的 HTTP web 客户端的所有片断。现在让我们看看如何将它们整合到一起。

  • 11.10. 小结

    openanything.py 及其函数现在可以完美地工作了。

第十二章 SOAP Web 服务

  • 12.1. 概览

    你用 Google,对吧?它是一个很流行的搜索引擎。你是否希望能以程序化的方式访问 Google 的搜索结果呢?现在你能做到了。下面是一个用 Python 搜索 Google 的程序。

  • 12.2. 安装 SOAP 库

    与本书中的其他代码不同,本章依赖的库不是 Python 预安装的。

  • 12.3. 步入 SOAP

    调用远程函数是 SOAP 的核心功能。有很多提供公开 SOAP 访问的服务器提供用于展示的简单功能。

  • 12.4. SOAP 网络服务查错

    SOAP 提供了一个很方便的方法用以查看背后的情形。

  • 12.5. WSDL 介绍

    SOAPProxy 类本地方法调用并透明地转向到远程 SOAP 方法。正如你所看到的,这是很多的工作,SOAPProxy 快速和透明地完成他们。它没有做到的是提供方法自省的手段。

  • 12.6. 以 WSDL 进行 SOAP 内省

    就像网络服务舞台上的所有事物,WSDL 也经历了一个充满明争暗斗而且漫长多变的历史。我不打算讲述这段令我伤心的历史。还有一些其他的标准提供相同的支持,但 WSDL 还是胜出,所以我们还是来学习一下如何使用它。

  • 12.7. 搜索 Google

    让我们回到这章开始时你看到的那段代码,获得比当前气温更有价值和令人振奋的信息。

  • 12.8. SOAP 网络服务故障排除

    是的,SOAP 网络服务的世界中也不总是欢乐和阳光。有时候也会有故障。

  • 12.9. 小结

    SOAP 网络服务是很复杂的,雄心勃勃的它试图涵盖网络服务的很多不同应用。这一章我们接触了它的一个简单应用。

第十三章 单元测试

  • 13.1. 罗马数字程序介绍 II

    在前面的章节中,通过阅读代码,你迅速“深入”,以最快的速度理解了各个程序。既然你已对 Python 有了一定的了解,那么接下来让我们看看程序开发之前 的工作。

  • 13.2. 深入

    现在你已经定义了你的转换程序所应有的功能,下面一步会有点儿出乎你的意料:你将要开发一个测试组件 (test suite) 来测试你未来的函数以确保它们工作正常。没错:你将为还未开发的程序开发测试代码。

  • 13.3. romantest.py 介绍

    这是将被开发并保存为 roman.py 的罗马数字转换程序的完整测试组件 (test suite)。很难立刻看出它们是如何协同工作的,似乎所有类或者方法之间都没有关系。这是有原因的,而且你很快就会明了。

  • 13.4. 正面测试 (Testing for success)

    单元测试的基础是构建独立的测试用例 (test case)。一个测试用例只回答一个关于被测试代码的问题。

  • 13.5. 负面测试 (Testing for failure)

    使用有效输入确保函数成功通过测试还不够,你还需要测试无效输入导致函数失败的情形。但并不是任何失败都可以,必须如你预期地失败。

  • 13.6. 完备性检测 (Testing for sanity)

    你经常会发现一组代码中包含互逆的转换函数,一个把 A 转换为 B ,另一个把 B 转换为 A。在这种情况下,创建“完备性检测”可以使你在由 A 转 B 再转 A 的过程中不会出现丢失精度或取整等错误。

第十四章 测试优先编程

  • 14.1. roman.py, 第 1 阶段

    到目前为止,单元测试已经完成,是时候开始编写被单元测试测试的代码了。你将分阶段地完成这个工作,因此开始时所有的单元测试都是失败的,但在逐步完成 roman.py 的同时你会看到它们一个个地通过测试。

  • 14.2. roman.py, 第 2 阶段

    现在你有了 roman 模块的大概框架,到了开始写代码以通过测试的时候了。

  • 14.3. roman.py, 第 3 阶段

    现在 toRoman 对于有效的输入 (13999 整数) 已能正确工作,是正确处理那些无效输入 (任何其他输入) 的时候了。

  • 14.4. roman.py, 第 4 阶段

    现在 toRoman 完成了,是开始编写 fromRoman 的时候了。感谢那个将每个罗马数字和对应整数关连的完美数据结构,这个工作不比 toRoman 函数复杂。

  • 14.5. roman.py, 第 5 阶段

    现在 fromRoman 对于有效输入能够正常工作了,是揭开最后一个谜底的时候了:使它正常工作于无效输入的情况下。这意味着要找出一个方法检查一个字符串是不是有效的罗马数字。这比 toRoman 中验证有效的数字输入困难,但是你可以使用一个强大的工具:正则表达式。

第十五章 重构

  • 15.1. 处理 bugs

    尽管你很努力地编写全面的单元测试,但是 bug 还是会出现。我所说的 “bug” 是什么呢?Bug 是你还没有编写的测试用例。

  • 15.2. 应对需求变化

    尽管你竭尽努力地分析你的客户,并点灯熬油地提炼出精确的需求,但需求还是会是不断变化。大部分客户在看到产品前不知道他们想要什么。即便知道,也不擅于精确表述出他们的有效需求。即便能表述出来,他们在下一个版本一定会要求更多的功能。因此你需要做好更新测试用例的准备以应对需求的改变。

  • 15.3. 重构

    全面的单元测试带来的最大好处不是你的全部测试用例最终通过时的成就感;也不是被责怪破坏了别人的代码时能够证明 自己的自信。最大的好处是单元测试给了你自由去无情地重构。

  • 15.4. 后记

    聪明的读者在学习前一节时想得会更深入一层。现在写的这个程序中最令人头痛的性能负担是正则表达式,但它是必需的,因为没有其它方法来识别罗马数字。但是,它们只有 5000 个,为什么不一次性地构建一个查询表来读取?不必用正则表达式凸现了这个主意的好处。你建立了整数到罗马数字查询表的时候,罗马数字到整数的逆向查询表也构建了。

  • 15.5. 小结

    单元测试是一个强大的概念,使用得当的话既可以减少维护成本又可以增加长期项目的灵活性。同样重要的是要意识到单元测试并不是“灵丹妙药”,也不是“银弹”。编写好的测试用例很困难,保持其更新更需要磨练 (特别是当顾客对修复严重的 Bug 大呼小叫之时)。单元测试不是其它形式测试的替代品,比如说功能性测试、集成测试以及可用性测试。但它切实可行且功效明显,一旦相识,你会反问为什么以往没有应用它。

第十六章 函数编程

  • 16.1. 概览

    在 第十三章 单元测试 中,你学会了单元测试的哲学。在 第十四章 测试优先编程 中你步入了 Python 基本的单元测试操作,在 第十五章 重构 部分,你看到单元测试如何令大规模重构变得容易。本章将在这些程序样例的基础上,集中关注于超越单元测试本身的更高级的 Python 特有技术。

  • 16.2. 找到路径

    从命令行运行 Python 代码时,知道所运行代码在磁盘上的存储位置有时候是有必要的。

  • 16.3. 重识列表过滤

    你已经熟识了应用列表解析来过滤列表。这里介绍的是达到相同效果的另一种令很多人感觉清晰的实现方法。

  • 16.4. 重识列表映射

    你对使用列表解析映射列表的做法已经熟知。另一种方法可以完成同样的工作:使用内建 map 函数。它的工作机理和 filter 函数类似。

  • 16.5. 数据中心思想编程

    现在的你,可能正抓耳挠腮地狠想,为什么这样比使用 for 循环和直接调用函数好。这是一个非常好的问题。通常这是一个程序观问题。使用 mapfilter 强迫你围绕数据进行思考。

  • 16.6. 动态导入模块

    好了,大道理谈够了。让我们谈谈动态导入模块吧。

  • 16.7. 全部放在一起

    你已经学习了足够的知识,现在来分析本章样例代码的前七行:读取一个目录并从中导入选定的模块。

  • 16.8. 小结

    regression.py 程序及其输出到现在应该很清楚了。

第十七章 动态函数

  • 17.1. 概览

    我想谈谈名词复数。还有,返回其它函数的函数,高级的正则表达式和生成器 (Generator)。生成器是 Python 2.3 新引入的。但首先还是让我们先来谈谈如何生成名词复数。

  • 17.2. plural.py, 第 1 阶段

    你所针对的单词 (至少在英语中) 是字符串和字符。你还需要规则来找出不同的字符 (字母) 组合,并对它们进行不同的操作。这听起来像是正则表达式的工作。

  • 17.3. plural.py, 第 2 阶段

    现在你将增加一个抽象过程。你从定义一个规则列表开始:如果这样,就做那个,否则判断下一规则。让我们暂时将程序一部分复杂化以便使另一部分简单化。

  • 17.4. plural.py, 第 3 阶段

    将每个匹配和规则应用分别制作成函数没有必要。你从来不会直接调用它们:你把它们定义于 rules 列表之中并从那里调用它们。让我们隐去它们的函数名而抓住规则定义的主线。

  • 17.5. plural.py, 第 4 阶段

    让我们精炼出代码中的重复之处,以便更容易地定义新规则。

  • 17.6. plural.py, 第 5 阶段

    你已经精炼了所有重复代码,也尽可能地把复数规则提炼到定义一个字符串列表。接下来的步骤是把这些字符串提出来放在另外的文件中,从而可以和使用它们的代码分开来维护。

  • 17.7. plural.py, 第 6 阶段

    现在你已准备好探讨生成器 (Generator) 了。

  • 17.8. 小结

    这一章中我们探讨了几个不同的高级技术。它们并不都适用于任何情况。

第十八章 性能优化

  • 18.1. 概览

    由于代码优化过程中存在太多的不明确因素,以至于你很难清楚该从何入手。

  • 18.2. 使用 timeit 模块

    关于 Python 代码优化你需要知道的最重要问题是,决不要自己编写计时函数。

  • 18.3. 优化正则表达式

    Soundex 函数的第一件事是检查输入是否是一个空字符串。怎样做是最好的方法?

  • 18.4. 优化字典查找

    Soundex 算法的第二步是依照特定规则将字符转换为数字。做到这点最好的方法是什么?

  • 18.5. 优化列表操作

    Soundex 算法的第三步是去除连续重复字符。怎样做是最佳方法?

  • 18.6. 优化字符串操作

    Soundex 算法的最后一步是对短结果补零和截短长结果。最佳的做法是什么?

  • 18.7. 小结

    这一章展示了性能优化的几个重要方面,这里是就 Python 而言,但它们却普遍适用。

附录 C. 技巧和窍门

第一章 安装 Python

第二章 第一个 Python 程序

  • 2.1. 概览

    提示 在 Windows 的 ActivePython IDE 中,可以选择 File->Run... (Ctrl-R) 来运行 Python 程序。输出结果将显示在交互窗口中。

    提示 在 Mac OS 的 Python IDE 中,可以选择 Python->Run window... (Cmd-R) 来运行 Python 程序,但首先要设置一个重要的选项。在 IDE 中打开 .py 模块,点击窗口右上角的黑色三角,弹出这个模块的选项菜单,然后将 Run as main 选中。 这个设置是同模块一同保存的,所以对于每个模块您都需要这样做。

    提示 在 UNIX 兼容的操作系统中 (包括 Mac OS X),可以通过命令行:pythonodbchelper.py`` 运行模块。

  • 2.2. 函数声明

    注意 在 Visual Basic 中,函数 (有返回值) 以 function 开始,而子程序 (无返回值) 以 sub 开始。在 Python 中没有子程序。只有函数,所有的函数都有返回值 (尽管可能为 None),并且所有的函数都以 def 开始。

    注意 在 Java、C++ 和其他静态类型语言中,必须要指定函数返回值和每个函数参数的数据类型。在 Python 中,永远也不需要明确指定任何东西的数据类型。Python 会根据赋给它的值在内部将其数据类型记录下来。

  • 2.3. 文档化函数

    注意 三重引号也是一种定义既包含单引号又包含双引号的字符串的简单方法,就像 Perl 中的 qq/.../

    注意 许多 Python IDE 使用 doc string 来提供上下文敏感的文档信息,所以当键入一个函数名时,它的 doc string 显示为一个工具提示。这一点可以说非常有用,但是它的好坏取决于您书写的 doc string 的好坏。

  • 2.4. 万物皆对象

    注意 在 Python 中的 import 就像 Perl 中的 requireimport 一个 Python 模块后,您就可以使用 _module_._function_ 来访问它的函数;require 一个 Perl 模块后,您就可以使用 _module_::_function_ 来访问它的函数。

  • 2.5. 代码缩进

    注意 Python 使用硬回车来分割语句,冒号和缩进来分割代码块。C++ 和 Java 使用分号来分割语句,花括号来分割代码块。

  • 2.6. 测试模块

    注意 与 C 一样,Python 使用 == 做比较,使用 = 做赋值。与 C 不一样,Python 不支持行内赋值,所以不会出现想要进行比较却意外地出现赋值的情况。

    提示 在 MacPython 上,需要一个额外的步聚来使得 if __name__ 技巧有效。点击窗口右上角的黑色三角,弹出模块的属性菜单,确认 Run as main 被选中。

第三章 内置数据类型

  • 3.1. Dictionary 介绍

    注意 Python 中的 dictionary 就像 Perl 中的 hash (哈希数组)。在 Perl 中,存储哈希值的变量总是以 % 字符开始;在 Python 中,变量可以任意取名,并且 Python 在内部会记录下其数据类型。

    注意 Python 中的 dictionary 像 Java 中的 Hashtable 类的实例。

    注意 Python 中的 dictionary 像 Visual Basic 中的 Scripting.Dictionary 对象的实例。

  • 3.1.2. Dictionary 的修改

    注意 Dictionary 没有元素顺序的概念。说元素 “顺序乱了” 是不正确的,它们只是序偶的简单排列。这是一个重要的特性,它会在您想要以一种特定的,可重现的顺序 (像以 key 的字母表顺序) 存取 dictionary 元素的时候骚扰您。有一些实现这些要求的方法,它们只是没有加到 dictionary 中去。

  • 3.2. List 介绍

    注意 Python 的 list 如同 Perl 中的数组。在 Perl 中,用来保存数组的变量总是以 @ 字符开始;在 Python 中,变量可以任意取名,并且 Python 在内部会记录下其数据类型。

    注意 Python 中的 list 更像 Java 中的数组 (您可以简单地这样理解,但 Python 中的 list 远比 Java 中的数组强大)。一个更好的类比是 ArrayList 类,它可以保存任意对象,并且可以在增加新元素时动态扩展。

  • 3.2.3. 在 list 中搜索

    注意 在 2.2.1 版本之前,Python 没有单独的布尔数据类型。为了弥补这个缺陷,Python 在布尔环境 (如 if 语句) 中几乎接受所有东西,遵循下面的规则:

    • 0 为 false; 其它所有数值皆为 true。
    • 空串 ("") 为 false; 其它所有字符串皆为 true。
    • 空 list ([]) 为 false; 其它所有 list 皆为 true。
    • 空 tuple (()) 为 false; 其它所有 tuple 皆为 true。
    • 空 dictionary ({}) 为 false; 其它所有 dictionary 皆为 true。

    这些规则仍然适用于 Python 2.2.1 及其后续版本,但现在您也可以使用真正的布尔值,它的值或者为 True 或者为 False。请注意第一个字母是大写的;这些值如同在 Python 中的其它东西一样都是大小写敏感的。

  • 3.3. Tuple 介绍

    注意 Tuple 可以转换成 list,反之亦然。内置的 tuple 函数接收一个 list,并返回一个有着相同元素的 tuple。而 list 函数接收一个 tuple 返回一个 list。从效果上看,tuple 冻结一个 list,而 list 解冻一个 tuple。

  • 3.4. 变量声明

    注意 当一条命令用续行符 (“\”) 分割成多行时,后续的行可以以任何方式缩进,此时 Python 通常的严格的缩进规则无需遵守。如果您的 Python IDE 自由对后续行进行了缩进,您应该把它当成是缺省处理,除非您有特别的原因不这么做。

  • 3.5. 格式化字符串

    注意 在 Python 中,字符串格式化使用与 C 中 sprintf 函数一样的语法。

  • 3.7. 连接 list 与分割字符串

    小心 join 只能用于元素是字符串的 list;它不进行任何的强制类型转换。连接一个存在一个或多个非字符串元素的 list 将引发一个异常。

    提示 _anystring_.split(_delimiter_, 1) 是一个有用的技术,在您想要搜索一个子串,然后分别处理字符前半部分 (即 list 中第一个元素) 和后半部分 (即 list 中第二个元素) 时,使用这个技术。

第四章 自省的威力

  • 4.2. 使用可选参数和命名参数

    注意 调用函数时唯一必须做的事情就是为每一个必备参数指定值 (以某种方式);以何种具体的方式和顺序都取决于你。

  • 4.3.3. 内置函数

    注意 Python 提供了很多出色的参考手册,你应该好好地精读一下所有 Python 提供的必备模块。对于其它大部分语言,你会发现自己要常常回头参考手册或者 man 页来提醒自己如何使用这些模块,但是 Python 不同于此,它很大程度上是自文档化的。

  • 4.7. 使用 lambda 函数

    注意 lambda 函数是一种风格问题。不一定非要使用它们;任何能够使用它们的地方,都可以定义一个单独的普通函数来进行替换。我将它们用在需要封装特殊的、非重用代码上,避免令我的代码充斥着大量单行函数。

  • 4.8. 全部放在一起

    注意 在 SQL 中,你必须使用 IS NULL 代替 = NULL 进行 null 值比较。在 Python,你可以使用 == None 或者 is None 进行比较,但是 is None 更快。

第五章 对象和面向对象

  • 5.2. 使用 from module import 导入模块

    注意 Python 中的 from _module_ import * 像 Perl 中的 use _module_ ;Python 中的 import _module_ 像 Perl 中的 require _module_

    注意 Python 中的 from _module_ import * 像 Java 中的 import _module_.* ;Python 中的 import _module_ 像 Java 中的 import _module_

    小心 尽量少用 from module import * ,因为判定一个特殊的函数或属性是从哪来的有些困难,并且会造成调试和重构都更困难。

  • 5.3. 类的定义

    注意 在 Python 中的 pass 语句就像 Java 或 C 中的大括号空集 ({})。

    注意 在 Python 中,类的基类只是简单地列在类名后面的小括号里。不像在 Java 中有一个特殊的 extends 关键字。

  • 5.3.1. 初始化并开始类编码

    注意 习惯上,任何 Python 类方法的第一个参数 (对当前实例的引用) 都叫做 self。这个参数扮演着 C++ 或 Java 中的保留字 this 的角色,但 self 在 Python 中并不是一个保留字,它只是一个命名习惯。虽然如此,也请除了 self 之外不要使用其它的名字,这是一个非常坚固的习惯。

  • 5.3.2. 了解何时去使用 self 和 init

    注意 __init__ 方法是可选的,但是一旦你定义了,就必须记得显示调用父类的 __init__ 方法 (如果它定义了的话)。这样更是正确的:无论何时子类想扩展父类的行为,后代方法必须在适当的时机,使用适当的参数,显式调用父类方法。

  • 5.4. 类的实例化

    注意 在 Python 中,创建类的实例只要调用一个类,仿佛它是一个函数就行了。不像 C++ 或 Java 有一个明确的 new 操作符。

  • 5.5. 探索 UserDict:一个封装类

    提示 在 Windows 下的 ActivePython IDE 中,你可以快速打开在你的库路径中的任何模块,使用 File->Locate... (Ctrl-L)。

    注意 Java 和 Powerbuilder 支持通过参数列表的重载,也就是 一个类可以有同名的多个方法,但这些方法或者是参数个数不同,或者是参数的类型不同。其它语言 (最明显如 PL/SQL) 甚至支持通过参数名的重载,也就是 一个类可以有同名的多个方法,这些方法有相同类型,相同个数的参数,但参数名不同。Python 两种都不支持,总之是没有任何形式的函数重载。一个 __init__ 方法就是一个 __init__ 方法,不管它有什么样的参数。每个类只能有一个 __init__ 方法,并且如果一个子类拥有一个 __init__ 方法,它总是 覆盖父类的 __init__ 方法,甚至子类可以用不同的参数列表来定义它。

    注意 Python 的原作者 Guido 是这样解释方法覆盖的:“子类可以覆盖父类中的方法。因为方法没有特殊的优先级设置,父类中的一个方法在调用同类中的另一方法时,可能其实调用到的却是一个子类中覆盖父类同名方法的方法。 (C++ 程序员可能会这样想:所有的 Python 方法都是虚函数。)”如果你不明白 (它令我颇感困惑),不必在意。我想我要跳过它。[3]

    小心 应该总是在 __init__ 方法中给一个实例的所有数据属性赋予一个初始值。这样做将会节省你在后面调试的时间,不必为捕捉因使用未初始化 (也就是不存在) 的属性而导致的 AttributeError 异常费时费力。

    注意 在 Python 2.2 之前的版本中,你不可以直接子类化字符串、列表以及字典之类的内建数据类型。作为补偿,Python 提供封装类来模拟内建数据类型的行为,比如:UserStringUserListUserDict。通过混合使用普通和特殊方法,UserDict 类能十分出色地模仿字典。在 Python 2.2 和其后的版本中,你可以直接从 dict 内建数据类型继承。本书 fileinfo_fromdict.py 中有这方面的一个例子。

  • 5.6.1. 获得和设置数据项

    注意 当在一个类中存取数据属性时,你需要限定属性名:self._attribute_。当调用类中的其它方法时,你属要限定方法名:self._method_

  • 5.7. 高级专用类方法

    注意 在 Java 中,通过使用 str1 == str2 可以确定两个字符串变量是否指向同一块物理内存位置。这叫做对象同一性,在 Python 中写为 str1 is str2。在 Java 中要比较两个字符串值,你要使用 str1.equals(str2);在 Python 中,你要使用 str1 == str2。某些 Java 程序员,他们已经被教授得认为,正是因为在 Java 中 == 是通过同一性而不是值进行比较,所以世界才会更美好。这些人要接受 Python 的这个“严重缺失”可能要花些时间。

    注意 其它的面向对象语言仅让你定义一个对象的物理模型 (“这个对象有 GetLength 方法”),而 Python 的专用类方法像 __len__ 允许你定义一个对象的逻辑模型 (“这个对象有一个长度”)。

  • 5.8. 类属性介绍

    注意 在 Java 中,静态变量 (在 Python 中叫类属性) 和实例变量 (在 Python 中叫数据属性) 两者都是紧跟在类定义之后定义的 (一个有 static 关键字,一个没有)。在 Python 中,只有类属性可以定义在这里,数据属性定义在 __init__ 方法中。

    注意 在 Python 中没有常量。如果你试图努力的话什么都可以改变。这一点满足 Python 的核心原则之一:坏的行为应该被克服而不是被取缔。如果你真正想改变 None 的值,也可以做到,但当无法调试的时候别来找我。

  • 5.9. 私有函数

    注意 在 Python 中,所有的专用方法 (像 __setitem__](specialclassmethods.html#fileinfo.specialmethods.setitem.example "例 5.13. setitem 专用方法")) 和内置属性 (像 __doc**) 遵守一个标准的命名习惯:开始和结束都有两个下划线。不要对你自已的方法和属性用这种方法命名;到最后,它只会把你 (或其它人) 搞乱。

第六章 异常和文件处理

  • 6.1. 异常处理

    注意 Python 使用 try...except 来处理异常,使用 raise 来引发异常。Java 和 C++ 使用 try...catch 来处理异常,使用 throw 来引发异常。

  • 6.5. 与目录共事

    注意 只要有可能,你就应该使用在 osos.path 中的函数进行文件、目录和路径的操作。这些模块是对平台相关模块的封装模块,所以像 os.path.split 这样的函数可以工作在 UNIX、Windows、Mac OS 和 Python 所支持的任一种平台上。

第七章 正则表达式

  • 7.4. 使用 {n,m} 语法

    注意 没有一个轻松的方法来确定两个正则表达式是否等价。你能采用的最好的办法就是列出很多的测试样例,确定这两个正则表达式对所有的相关输入都有相同的输出。在本书后面的章节,将更多地讨论如何编写测试样例。

第八章 HTML 处理

  • 8.2. sgmllib.py 介绍

    重要 Python 2.0 存在一个 bug,即 SGMLParser 完全不能识别声明 (handle_decl 永远不会调用),这就意味着 DOCTYPE 被静静地忽略掉了。这个错误在 Python 2.1 中改正了。

    提示 在 Windows 下的 ActivePython IDE 中,您可以在 “Run script” 对话框中指定命令行参数。用空格将多个参数分开。

  • 8.4. BaseHTMLProcessor.py 介绍

    重要 HTML 规范要求所有非 HTML (像客户端的 JavaScript) 必须包括在 HTML 注释中,但不是所有的页面都是这么做的 (而且所有的最新的浏览器也都容许不这样做) 。BaseHTMLProcessor 不允许这样,如果脚本嵌入得不正确,它将被当作 HTML 一样进行分析。例如,如果脚本包含了小于和等于号,SGMLParser 可能会错误地认为找到了标记和属性。SGMLParser 总是把标记名和属性名转换成小写,这样可能破坏了脚本,并且 BaseHTMLProcessor 总是用双引号来将属性封闭起来 (尽管原始的 HTML 文档可能使用单引号或没有引号) ,这样必然会破坏脚本。应该总是将您的客户端脚本放在 HTML 注释中进行保护。

  • 8.5. locals 和 globals

    重要 Python 2.2 引入了一种略有不同但重要的改变,它会影响名字空间的搜索顺序:嵌套的作用域。 在 Python 2.2 版本之前,当您在一个嵌套函数或 lambda 函数中引用一个变量时,Python 会在当前 (嵌套的或 lambda) 函数的名字空间中搜索,然后在模块的名字空间。Python 2.2 将只在当前 (嵌套的或 lambda) 函数的名字空间中搜索,然后是在父函数的名字空间 中搜索,接着是模块的名字空间中搜索。Python 2.1 可 以两种方式工作,缺省地,按 Python 2.0 的方式工作。但是您可以把下面一行代码增加到您的模块头部,使您的模块工作起来像 Python 2.2 的方式:

    from __future__ import nested_scopes 
    

    注意 使用 localsglobals 函数,通过提供变量的字符串名字您可以动态地得到任何变量的值。这种方法提供了这样的功能:getattr 函数允许您通过提供函数的字符串名来动态地访问任意的函数。

  • 8.6. 基于 dictionary 的字符串格式化

    重要 使用 locals 来应用基于 dictionary 的字符串格式化是一种方便的作法,它可以使复杂的字符串格式化表达式更易读。但它需要花费一定的代价。在调用 locals 方面有一点性能上的问题,这是由于 [locals 创建了局部名字空间的一个拷贝引起的。

第九章 XML 处理

  • 9.2. 包

    注意 一个包是一个其中带有特殊文件 __init__.py 的目录。__init__.py 文件定义了包的属性和方法。其实它可以什么也不定义;可以只是一个空文件,但是必须要存在。如果 __init__.py 不存在,这个目录就仅仅是一个目录,而不是一个包,它就不能被导入或者包含其它的模块和嵌套包。

  • 9.6. 访问元素属性

    注意 这部分由于某个含义重叠的术语可能让人有点糊涂。在一个 XML 文档中,元素可以有属性,而 Python 对象也有属性。当你解析一个 XML 文档时,你得到了一组 Python 对象,它们代表 XML 文档中的所有片段,同时有些 Python 对象代表 XML 元素的属性。但是表示 (XML) 属性的 (Python) 对象也有 (Python) 属性,它们用于访问对象表示的 (XML) 属性。我告诉过你它让人糊涂。我会公开提出关于如何更明显地区分这些不同的建议。

    注意 类似于字典,一个 XML 元素的属性没有顺序。属性可以以某种顺序偶然 列在最初的 XML 文档中,而在 XML 文档解析为 Python 对象时,Attr 对象以某种顺序偶然 列出,这些顺序都是任意的,没有任何特别的含义。你应该总是使用名称来访问单个属性,就像字典的键一样。

第十章 脚本和流

第十一章 HTTP Web 服务

  • 11.6. 处理 Last-Modified 和 ETag

    注意 在这些例子中,HTTP 服务器同时支持 Last-ModifiedETag 头信息,但并非所有的服务器皆如此。作为一个 web 服务的客户端,你应该为支持两种头信息做准备,但是你的程序也应该为服务器仅支持其中一种头信息或两种头信息都不支持而做准备。

第十二章 SOAP Web 服务

第十三章 单元测试

  • 13.2. 深入

    注意 Python 2.1 和之后的版本已经包含了 unittest。Python 2.0 用户则可以从 pyunit.sourceforge.net下载。

第十四章 测试优先编程

  • 14.3. roman.py, 第 3 阶段

    注意 全面的单元测试能够告诉你的最重要的事情是什么时候停止编写代码。当一个函数的所有单元测试都通过了,停止编写这个函数。一旦整个模块的单元测试通过了,停止编写这个模块。

  • 14.5. roman.py, 第 5 阶段

    注意 当所有测试都通过了,停止编程。

第十五章 重构

  • 15.3. 重构

    注意 在需要多次使用同一个正则表达式的情况下,应该将它进行编译以获得一个 pattern 对象,然后直接调用这个 pattern 对象的方法。

第十六章 函数编程

  • 16.2. 找到路径

    注意 传递给 os.path.abspath 的路径名和文件名可以不存在。

    注意 os.path.abspath 不仅构建完整路径名,还能格式化路径名。这意味着如果你正工作于 /usr/ 目录,os.path.abspath('bin/../local/bin') 将会返回 /usr/local/bin。它把路径名格式化为尽可能简单的形式。如果你只是希望简单地返回这样的格式化路径名而不需要完整路径名,可以使用 os.path.normpath

    注意 就像 osos.path 模块的其他函数,os.path.abspath 是跨平台的。如果你是在 Windows (使用反斜杠作为路径符号) 或 Mac OS (使用冒号) 上运行,它们同样工作,只是将获得与我稍有不同的结果。os 的所有函数都是这样的。

第十七章 动态函数

第十八章 性能优化

  • 18.2. 使用 timeit 模块

    提示 你可以在命令行使用 timeit 模块来测试一个已存在的 Python 程序,而不需要修改代码。在 docs.python.org/lib/node396.html 查看文档中关于命令行选项的内容。

    提示 timeit 模块只有在你知道哪段代码需要优化时使用。如果你有一个很大的 Python 程序并且不知道你的性能问题所在,查看 hotshot 模块

附录 D. 示例清单

附录 D. 示例清单

第一章 安装 Python

  • 1.3. Mac OS X 上的 Python
    • 例 1.1. 两个 Python 版本
  • 1.5. RedHat Linux 上的 Python
    • 例 1.2. 在 RedHat Linux 9 上安装
  • 1.6. Debian GNU/Linux 上的 Python
    • 例 1.3. 在 Debian GNU/Linux 上安装
  • 1.7. 从源代码安装 Python
    • 例 1.4. 从源代码安装
  • 1.8. 使用 Python 的交互 Shell
    • 例 1.5. 初次使用交互 Shell

第二章 第一个 Python 程序

  • 2.1. 概览
    • 例 2.1. odbchelper.py
  • 2.3. 文档化函数
    • 例 2.2. 定义 buildConnectionString 函数的 doc string
  • 2.4. 万物皆对象
    • 例 2.3. 访问 buildConnectionString 函数的 doc string
  • 2.4.1. 模块导入的搜索路径
    • 例 2.4. 模块导入的搜索路径
  • 2.5. 代码缩进
    • 例 2.5. 缩进 buildConnectionString 函数
    • 例 2.6. if 语句

第三章 内置数据类型

  • 3.1.1. Dictionary 的定义
    • 例 3.1. 定义 Dictionary
  • 3.1.2. Dictionary 的修改
    • 例 3.2. 修改 Dictionary
    • 例 3.3. Dictionary 的 key 是大小写敏感的
    • 例 3.4. 在 dictionary 中混用数据类型
  • 3.1.3. 从 dictionary 中删除元素
    • 例 3.5. 从 dictionary 中删除元素
  • 3.2.1. List 的定义
    • 例 3.6. 定义 List
    • 例 3.7. 负的 list 索引
    • 例 3.8. list 的分片 (slice)
    • 例 3.9. Slice 简写
  • 3.2.2. 向 list 中增加元素
    • 例 3.10. 向 list 中增加元素
    • 例 3.11. extend (扩展) 与 append (追加) 的差别
  • 3.2.3. 在 list 中搜索
    • 例 3.12. 搜索 list
  • 3.2.4. 从 list 中删除元素
    • 例 3.13. 从 list 中删除元素
  • 3.2.5. 使用 list 的运算符
    • 例 3.14. List 运算符
  • 3.3. Tuple 介绍
    • 例 3.15. 定义 tuple
    • 例 3.16. Tuple 没有方法
  • 3.4. 变量声明
    • 例 3.17. 定义 myParams 变量
  • 3.4.1. 变量引用
    • 例 3.18. 引用未赋值的变量
  • 3.4.2. 一次赋多值
    • 例 3.19. 一次赋多值
    • 例 3.20. 连续值赋值
  • 3.5. 格式化字符串
    • 例 3.21. 字符串的格式化
    • 例 3.22. 字符串格式化与字符串连接的比较
    • 例 3.23. 数值的格式化
  • 3.6. 映射 list
    • 例 3.24. List 解析介绍
    • 例 3.25. keys, values 和 items 函数
    • 例 3.26. buildConnectionString 中的 list 解析
  • 3.7. 连接 list 与分割字符串
    • 例 3.27. odbchelper.py 的输出结果
    • 例 3.28. 分割字符串

第四章 自省的威力

  • 4.1. 概览
    • 例 4.1. apihelper.py
    • 例 4.2. apihelper.py 的用法示例
    • 例 4.3. apihelper.py 的高级用法
  • 4.2. 使用可选参数和命名参数
    • 例 4.4. info 的有效调用
  • 4.3.1. type 函数
    • 例 4.5. type 介绍
  • 4.3.2. str 函数
    • 例 4.6. str 介绍
    • 例 4.7. dir 介绍
    • 例 4.8. callable 介绍
  • 4.3.3. 内置函数
    • 例 4.9. 内置属性和内置函数
  • 4.4. 通过 getattr 获取对象引用
    • 例 4.10. getattr 介绍
  • 4.4.1. 用于模块的 getattr
    • 例 4.11. apihelper.py 中的 getattr 函数
  • 4.4.2. getattr 作为一个分发者
    • 例 4.12. 使用 getattr 创建分发者
    • 例 4.13. getattr 缺省值
  • 4.5. 过滤列表
    • 例 4.14. 列表过滤介绍
  • 4.6. and 和 or 的特殊性质
    • 例 4.15. and 介绍
    • 例 4.16. or 介绍
  • 4.6.1. 使用 and-or 技巧
    • 例 4.17. and-or 技巧介绍
    • 例 4.18. and-or 技巧无效的场合
    • 例 4.19. 安全使用 and-or 技巧
  • 4.7. 使用 lambda 函数
    • 例 4.20. lambda 函数介绍
  • 4.7.1. 真实世界中的 lambda 函数
    • 例 4.21. split 不带参数
  • 4.8. 全部放在一起
    • 例 4.22. 动态得到 doc string
    • 例 4.23. 为什么对一个 doc string 使用 str ?
    • 例 4.24. ljust 方法介绍
    • 例 4.25. 打印列表

第五章 对象和面向对象

  • 5.1. 概览
    • 例 5.1. fileinfo.py
  • 5.2. 使用 from module import 导入模块
    • 例 5.2. import module vs. from module import
  • 5.3. 类的定义
    • 例 5.3. 最简单的 Python 类
    • 例 5.4. 定义 FileInfo 类
  • 5.3.1. 初始化并开始类编码
    • 例 5.5. 初始化 FileInfo 类
    • 例 5.6. 编写 FileInfo 类
  • 5.4. 类的实例化
    • 例 5.7. 创建 FileInfo 实例
  • 5.4.1. 垃圾回收
    • 例 5.8. 尝试实现内存泄漏
  • 5.5. 探索 UserDict:一个封装类
    • 例 5.9. 定义 UserDict 类
    • 例 5.10. UserDict 常规方法
    • 例 5.11. 直接继承自内建数据类型 dict
  • 5.6.1. 获得和设置数据项
    • 例 5.12. getitem 专用方法
    • 例 5.13. setitem 专用方法
    • 例 5.14. 在 MP3FileInfo 中覆盖 setitem
    • 例 5.15. 设置 MP3FileInfo 的 name
  • 5.7. 高级专用类方法
    • 例 5.16. UserDict 中更多的专用方法
  • 5.8. 类属性介绍
    • 例 5.17. 类属性介绍
    • 例 5.18. 修改类属性
  • 5.9. 私有函数
    • 例 5.19. 尝试调用一个私有方法

第六章 异常和文件处理

  • 6.1. 异常处理
    • 例 6.1. 打开一个不存在的文件
  • 6.1.1. 为其他用途使用异常
    • 例 6.2. 支持特定平台功能
  • 6.2. 与文件对象共事
    • 例 6.3. 打开文件
  • 6.2.1. 读取文件
    • 例 6.4. 读取文件
  • 6.2.2. 关闭文件
    • 例 6.5. 关闭文件
  • 6.2.3. 处理 I/O 错误
    • 例 6.6. MP3FileInfo 中的文件对象
  • 6.2.4. 写入文件
    • 例 6.7. 写入文件
  • 6.3. for 循环
    • 例 6.8. for 循环介绍
    • 例 6.9. 简单计数
    • 例 6.10. 遍历 dictionary
    • 例 6.11. MP3FileInfo 中的 for 循环
  • 6.4. 使用 sys.modules
    • 例 6.12. sys.modules 介绍
    • 例 6.13. 使用 sys.modules
    • 例 6.14. module 类属性
    • 例 6.15. fileinfo.py 中的 sys.modules
  • 6.5. 与目录共事
    • 例 6.16. 构造路径名
    • 例 6.17. 分割路径名
    • 例 6.18. 列出目录
    • 例 6.19. 在 fileinfo.py 中列出目录
    • 例 6.20. 使用 glob 列出目录
  • 6.6. 全部放在一起
    • 例 6.21. listDirectory

第七章 正则表达式

  • 7.2. 个案研究:街道地址
    • 例 7.1. 在字符串的结尾匹配
    • 例 7.2. 匹配整个单词
  • 7.3.1. 校验千位数
    • 例 7.3. 校验千位数
  • 7.3.2. 校验百位数
    • 例 7.4. 检验百位数
  • 7.4. 使用 {n,m} 语法
    • 例 7.5. 老方法:每一个字符都是可选的
    • 例 7.6. 一个新的方法:从 n 到 m
  • 7.4.1. 校验十位数和个位数
    • 例 7.7. 校验十位数
    • 例 7.8. 用 {n,m} 语法确认罗马数字
  • 7.5. 松散正则表达式
    • 例 7.9. 带有内联注释 (Inline Comments) 的正则表达式
  • 7.6. 个案研究:解析电话号码
    • 例 7.10. 发现数字
    • 例 7.11. 发现分机号
    • 例 7.12. 处理不同分隔符
    • 例 7.13. 处理没有分隔符的数字
    • 例 7.14. 处理开始字符
    • 例 7.15. 电话号码,无论何时我都要找到它
    • 例 7.16. 解析电话号码 (最终版本)

第八章 HTML 处理

  • 8.1. 概览
    • 例 8.1. BaseHTMLProcessor.py
    • 例 8.2. dialect.py
    • 例 8.3. dialect.py 的输出结果
  • 8.2. sgmllib.py 介绍
    • 例 8.4. sgmllib.py 的样例测试
  • 8.3. 从 HTML 文档中提取数据
    • 例 8.5. urllib 介绍
    • 例 8.6. urllister.py 介绍
    • 例 8.7. 使用 urllister.py
  • 8.4. BaseHTMLProcessor.py 介绍
    • 例 8.8. BaseHTMLProcessor 介绍
    • 例 8.9. BaseHTMLProcessor 输出结果
  • 8.5. locals 和 globals
    • 例 8.10. locals 介绍
    • 例 8.11. globals 介绍
    • 例 8.12. locals 是只读的,globals 不是
  • 8.6. 基于 dictionary 的字符串格式化
    • 例 8.13. 基于 dictionary 的字符串格式化介绍
    • 例 8.14. BaseHTMLProcessor.py 中的基于 dictionary 的字符串格式化
    • 例 8.15. 基于 dictionary 的字符串格式化的更多内容
  • 8.7. 给属性值加引号
    • 例 8.16. 给属性值加引号
  • 8.8. dialect.py 介绍
    • 例 8.17. 处理特别标记
    • 例 8.18. SGMLParser
    • 例 8.19. 覆盖 handle_data 方法
  • 8.9. 全部放在一起
    • 例 8.20. translate 函数,第一部分
    • 例 8.21. translate 函数,第二部分:奇妙而又奇妙
    • 例 8.22. translate 函数,第三部分

第九章 XML 处理

  • 9.1. 概览
    • 例 9.1. kgp.py
    • 例 9.2. toolbox.py
    • 例 9.3. kgp.py 的样例输出
    • 例 9.4. kgp.py 的简单输出
  • 9.2. 包
    • 例 9.5. 载入一个 XML 文档 (偷瞥一下)
    • 例 9.6. 包的文件布局
    • 例 9.7. 包也是模块
  • 9.3. XML 解析
    • 例 9.8. 载入一个 XML 文档 (这次是真的)
    • 例 9.9. 获取子节点
    • 例 9.10. toxml 用于任何节点
    • 例 9.11. 子节点可以是文本
    • 例 9.12. 把文本挖出来
  • 9.4. Unicode
    • 例 9.13. unicode 介绍
    • 例 9.14. 存储非 ASCII 字符
    • 例 9.15. sitecustomize.py
    • 例 9.16. 设置默认编码的效果
    • 例 9.17. 指定.py 文件的编码
    • 例 9.18. russiansample.xml
    • 例 9.19. 解析 russiansample.xml
  • 9.5. 搜索元素
    • 例 9.20. binary.xml
    • 例 9.21. getElementsByTagName 介绍
    • 例 9.22. 每个元素都是可搜索的
    • 例 9.23. 搜索实际上是递归的
  • 9.6. 访问元素属性
    • 例 9.24. 访问元素属性
    • 例 9.25. 访问单个属性

第十章 脚本和流

  • 10.1. 抽象输入源
    • 例 10.1. 从文件中解析 XML
    • 例 10.2. 解析来自 URL 的 XML
    • 例 10.3. 解析字符串 XML (容易但不灵活的方式)
    • 例 10.4. StringIO 介绍
    • 例 10.5. 解析字符串 XML (类文件对象方式)
    • 例 10.6. openAnything
    • 例 10.7. 在 kgp.py 中使用 openAnything
  • 10.2. 标准输入、输出和错误
    • 例 10.8. stdout 和 stderr 介绍
    • 例 10.9. 重定向输出
    • 例 10.10. 重定向错误信息
    • 例 10.11. 打印到 stderr
    • 例 10.12. 链接命令
    • 例 10.13. 在 kgp.py 中从标准输入读取
  • 10.3. 查询缓冲节点
    • 例 10.14. loadGrammar
    • 例 10.15. 使用 ref 元素缓冲
  • 10.4. 查找节点的直接子节点
    • 例 10.16. 查找直接子元素
  • 10.5. 根据节点类型创建不同的处理器
    • 例 10.17. 已解析 XML 对象的类名
    • 例 10.18. parse,通用 XML 节点分发器
    • 例 10.19. parse 分发器调用的函数
  • 10.6. 处理命令行参数
    • 例 10.20. sys.argv 介绍
    • 例 10.21. sys.argv 的内容
    • 例 10.22. getopt 介绍
    • 例 10.23. 在 kgp.py 中处理命令行参数

第十一章 HTTP Web 服务

  • 11.1. 概览
    • 例 11.1. openanything.py
  • 11.2. 避免通过 HTTP 重复地获取数据
    • 例 11.2. 用直接而原始的方法下载 feed
  • 11.4. 调试 HTTP web 服务
    • 例 11.3. 调试 HTTP
  • 11.5. 设置 User-Agent
    • 例 11.4. urllib2 介绍
    • 例 11.5. 给 Request 添加头信息
  • 11.6. 处理 Last-Modified 和 ETag
    • 例 11.6. 测试 Last-Modified
    • 例 11.7. 定义 URL 处理器
    • 例 11.8. 使用自定义 URL 处理器
    • 例 11.9. 支持 ETag/If-None-Match
  • 11.7. 处理重定向
    • 例 11.10. 没有重定向处理的情况下,访问 web 服务
    • 例 11.11. 定义重定向处理器
    • 例 11.12. 使用重定向处理器检查永久重定向
    • 例 11.13. 使用重定向处理器检查临时重定向
  • 11.8. 处理压缩数据
    • 例 11.14. 告诉服务器你想获得压缩数据
    • 例 11.15. 解压缩数据
    • 例 11.16. 从服务器直接解压缩数据
  • 11.9. 全部放在一起
    • 例 11.17. openanything 函数
    • 例 11.18. fetch 函数
    • 例 11.19. 使用 openanything.py

第十二章 SOAP Web 服务

  • 12.1. 概览
    • 例 12.1. search.py
    • 例 12.2. search.py 的使用样例
  • 12.2.1. 安装 PyXML
    • 例 12.3. 检验 PyXML 安装
  • 12.2.2. 安装 fpconst
    • 例 12.4. 检验 fpconst 安装
  • 12.2.3. 安装 SOAPpy
    • 例 12.5. 检验 SOAPpy 安装
  • 12.3. 步入 SOAP
    • 例 12.6. 获得现在的气温
  • 12.4. SOAP 网络服务查错
    • 例 12.7. SOAP 网络服务查错
  • 12.6. 以 WSDL 进行 SOAP 内省
    • 例 12.8. 揭示有效方法
    • 例 12.9. 揭示一个方法的参数
    • 例 12.10. 揭示方法返回值
    • 例 12.11. 通过 WSDL proxy 调用一个 SOAP 网络服务
  • 12.7. 搜索 Google
    • 例 12.12. 内省 Google 网络服务
    • 例 12.13. 搜索 Google
    • 例 12.14. 从 Google 获得次要信息
  • 12.8. SOAP 网络服务故障排除
    • 例 12.15. 以错误的设置调用 Proxy 方法
    • 例 12.16. 以错误参数调用方法
    • 例 12.17. 调用时方法所期待的返回值个数错误
    • 例 12.18. 调用方法返回一个应用特定的错误

第十三章 单元测试

  • 13.3. romantest.py 介绍
    • 例 13.1. romantest.py
  • 13.4. 正面测试 (Testing for success)
    • 例 13.2. testToRomanKnownValues
  • 13.5. 负面测试 (Testing for failure)
    • 例 13.3. 测试 toRoman 的无效输入
    • 例 13.4. 测试 fromRoman 的无效输入
  • 13.6. 完备性检测 (Testing for sanity)
    • 例 13.5. 以 toRoman 测试 fromRoman 的输出
    • 例 13.6. 大小写测试

第十四章 测试优先编程

  • 14.1. roman.py, 第 1 阶段
    • 例 14.1. roman1.py
    • 例 14.2. 以 romantest1.py 测试 roman1.py 的输出
  • 14.2. roman.py, 第 2 阶段
    • 例 14.3. roman2.py
    • 例 14.4. toRoman 如何工作
    • 例 14.5. 以 romantest2.py 测试 roman2.py 的输出
  • 14.3. roman.py, 第 3 阶段
    • 例 14.6. roman3.py
    • 例 14.7. 观察 toRoman 如何处理无效输入
    • 例 14.8. 用 romantest3.py 测试 roman3.py 的结果
  • 14.4. roman.py, 第 4 阶段
    • 例 14.9. roman4.py
    • 例 14.10. fromRoman 如何工作
    • 例 14.11. 用 romantest4.py 测试 roman4.py 的结果
  • 14.5. roman.py, 第 5 阶段
    • 例 14.12. roman5.py
    • 例 14.13. 用 romantest5.py 测试 roman5.py 的结果

第十五章 重构

  • 15.1. 处理 bugs
    • 例 15.1. 关于 Bug
    • 例 15.2. 测试 bug (romantest61.py)
    • 例 15.3. 用 romantest61.py 测试 roman61.py 的结果
    • 例 15.4. 修改 Bug (roman62.py)
    • 例 15.5. 用 romantest62.py 测试 roman62.py 的结果
  • 15.2. 应对需求变化
    • 例 15.6. 修改测试用例以适应新需求 (romantest71.py)
    • 例 15.7. 用 romantest71.py 测试 roman71.py 的结果
    • 例 15.8. 为新的需求编写代码 (roman72.py)
    • 例 15.9. 用 romantest72.py 测试 roman72.py 的结果
  • 15.3. 重构
    • 例 15.10. 编译正则表达式
    • 例 15.11. roman81.py 中已编译的正则表达式
    • 例 15.12. 用 romantest81.py 测试 roman81.py 的结果
    • 例 15.13. roman82.py
    • 例 15.14. 以 romantest82.py 测试 roman82.py 的结果
    • 例 15.15. roman83.py
    • 例 15.16. 用 romantest83.py 测试 roman83.py 的结果
  • 15.4. 后记
    • 例 15.17. roman9.py
    • 例 15.18. 用 romantest9.py 测试 roman9.py 的结果

第十六章 函数编程

  • 16.1. 概览
    • 例 16.1. regression.py
    • 例 16.2. regression.py 的样例输出
  • 16.2. 找到路径
    • 例 16.3. fullpath.py
    • 例 16.4. os.path.abspath 的进一步解释
    • 例 16.5. fullpath.py 的样例输出
    • 例 16.6. 在当前目录运行脚本
  • 16.3. 重识列表过滤
    • 例 16.7. filter 介绍
    • 例 16.8. regression.py 中的 filter
    • 例 16.9. 以列表解析法过滤
  • 16.4. 重识列表映射
    • 例 16.10. map 介绍
    • 例 16.11. map 与混合数据类型的列表
    • 例 16.12. regression.py 中的 map
  • 16.6. 动态导入模块
    • 例 16.13. 同时导入多个模块
    • 例 16.14. 动态导入模块
    • 例 16.15. 动态导入模块列表
  • 16.7. 全部放在一起
    • 例 16.16. regressionTest 函数
    • 例 16.17. 步骤 1:获得所有文件
    • 例 16.18. 步骤 2:找到你关注的多个文件
    • 例 16.19. 步骤 3:映射文件名到模块名
    • 例 16.20. 步骤 4:映射模块名到模块
    • 例 16.21. 步骤 5:将模块载入测试套件
    • 例 16.22. 步骤 6:告知 unittest 使用你的测试套件

第十七章 动态函数

  • 17.2. plural.py, 第 1 阶段
    • 例 17.1. plural1.py
    • 例 17.2. re.sub 介绍
    • 例 17.3. 回到 plural1.py
    • 例 17.4. 正则表达式中否定的更多应用
    • 例 17.5. 更多的 re.sub
  • 17.3. plural.py, 第 2 阶段
    • 例 17.6. plural2.py
    • 例 17.7. 剖析 plural 函数
  • 17.4. plural.py, 第 3 阶段
    • 例 17.8. plural3.py
  • 17.5. plural.py, 第 4 阶段
    • 例 17.9. plural4.py
    • 例 17.10. plural4.py 继续
    • 例 17.11. 剖析规则定义
    • 例 17.12. plural4.py 的完成
    • 例 17.13. 回头看 buildMatchAndApplyFunctions
    • 例 17.14. 调用函数时展开元组
  • 17.6. plural.py, 第 5 阶段
    • 例 17.15. rules.en
    • 例 17.16. plural5.py
  • 17.7. plural.py, 第 6 阶段
    • 例 17.17. plural6.py
    • 例 17.18. 介绍生成器
    • 例 17.19. 使用生成器替代递归
    • 例 17.20. for 循环中的生成器
    • 例 17.21. 生成器生成动态函数

第十八章 性能优化

  • 18.1. 概览
    • 例 18.1. soundex/stage1/soundex1a.py
  • 18.2. 使用 timeit 模块
    • 例 18.2. timeit 介绍
  • 18.3. 优化正则表达式
    • 例 18.3. 目前为止最好的结果:soundex/stage1/soundex1e.py
  • 18.4. 优化字典查找
    • 例 18.4. 目前的最佳结果:soundex/stage2/soundex2c.py
  • 18.5. 优化列表操作
    • 例 18.5. 目前的最佳结果:soundex/stage2/soundex2c.py