Python Cookbook第2版 中文版


第 1 文本伯 引言 感谢Fred L. Drake, Jr.,PythonLabs 于脚本语言来说,文本处理任务构了一个要的部,每个人都会意文本 处理非常有用每个人都会有一文本需要新格式化或者转化一种形式题 是,每个程序都一个程序有点,无论它们是多相似,想出一复用 的码段并用它来处理的文格式然是非常困难的 是文本 看来题有点简单得过了,实,们看到了文本,就知道了是文本,文 本是一串符,是它制数据之间的制数据是一串节 幸的是,所有的数据入程序中都是一串节没有函数能够帮助们确 定某一个定的节串是表文本,们能自创一试探的方法来判断那 数据是能够用文本的方式来处理一定确第 1.11 节就展示了一种试探的方法 Python 的符串是一串改的节或者符大多数们创的方法及处理 符串的方式都是把它们当做一串符来处理的,但是一节串是处理的 Unicode 符串是一串改的 Unicode 符由 Unicode 符串转到普通符串 或者由普通符串转化 Unicode 符串的过程是通过 codecs编码器-解码器象 来完的,在象及的很多标准转化方法中,符串被表示一串节 被称编码和符但是需要注意的是,Unicode 符串像节串那身兼 职第 1.20 节第 1.21 节和第 1.22 节展示了 Python 中 Unicode 的一基本方法 在假们的程序通过文境确认了它要处理的是文本程序一般会接收外部 输入,通常是好的方法们能够识别该文的原因是,它有个知的和 经定好了的格式在 UNIX 世界很普遍,或者它有个著的文扩展来指示出 国国 1 它内容的格式在 Windows 世界很普遍在有个题们必使用格式 种东西,要然面段话毫无意人们是认文本很简单吗? 们面个题实并没有所谓的纯文本的东西,使有,们 能会关心有例外,计算机语言学家在某情况能会研纯粹的文本 们的程序想处理的东西是包含在文本中的信息们关心的文本能包含了配置 命制命流程定命供人类阅读的文档甚制表信息包含配置数 据和一系列命的文本通常通过格的语法检查来验证,然才能定是 依赖中的信息向用户示输入文本中的错误够,是们要 论的题 供人类阅读的文档似乎比较简单,但然有很多节需要注意由于它们通常被写 自然语言的形式,它们的语法和法很难检查的文本能会使用的符 和编码,如果知道相关的编码信息,想检查出一段文本使用了何种符和编 码是极困难的,甚是能的然而,于自然语言文档的确呈,是非 常必要的自然语言文本有结构,但是种结构并明显而且需要你少懂一点 该种自然语言符了单词,单词形了子,然子构了段落,但然 有更大的结构单独的一个段落很难定,除非你知道文档的排定是每行 构一个段,是多行一个段?如果是者,们怎判断哪行构了一段? 段之间能会被空行缩或者他的殊符号隔开第 19.10 节就给出了一个读 文本文中由空行隔开段落的例子 制表信息就像自然语言文本一有很多值得论的地方,它实给输入的格式增 加了第个维度文本是线性的,是一串符,而是一个符的矩,每 一个单独的文本块被缩和来 基本的文本操作 就像他的数据格式,们需要用一的方式在的场合处理文本总地 来说,有种基本的操作 • 解析数据并将数据入程序内部的结构中 • 将数据某种方式转化一种相似的形式,数据本身发生了改 • 生全新的数据 有很多方式完解析,一别的解析器有效处理有多限制的数据格式, 并解析大量他的格式比如 RFC 2822 型的邮头格式参看 Python 标准的 rfc822 模块和由 ConfigParser 模块处理的配置文格式netrc 模块则供了一种解析 用有格式的解析器例子,个模块基于 shlex 模块shlex 供一个型的词器 tokenizer,用于一基本的语言,别合用来创建读的配置文及用户 2 第 1 在一个有交互和示能力的境输入命Python 标准中有很多类似的别解 析器,关于它们的使用示例和方法参看第 2 和第 13 Python 中有一式的 解析,它们依赖于一庞大的加包,阅读第 16 的引言部获得一个大 的了解 当们到文本的时候,们的第一个念头是,所谓文本处理,有时候就是文本 格式转换在本中,们将会看到一用于各种场合的转换方法有时们 要处理的文本是储于外部文中的,有时们则直接处理内中的符串 通过 Python 的 print 语,文象或者类文象的 write 方法,们轻易地产 生基于程序定结构的文本数据于那将输出文作一个传入参数的用, 们通常是用该程序的一个方法或者函数来完功能举个例子,函数像面 使用 print 语 print >>thefile, sometext thefile.write(sometext) 就将产生的输出写入到了文过,并是通常们所认的文本处理,因 在个过程中并没有文本输入本书有很多使用 print 和 write 的例子,在一的阅 读中你会看到更多的示例 文本的来源 当文本处于内的时候,如果数据量是很大,处理来相比较容易文本的搜 索轻易和快地跨越多行,而且用心搜索会超出缓的边界要们能 法文本处于内,并一种普通符串的形式呈,就充利用 string 象的各 种内建的操作来简单而快地处理文本 基于文的转化则需要一别的待,因需要考虑 I/O 性能和的开销,及实 需要入内的数据量当处理于磁盘的数据时,由于数据的尺,们常 常避免将整个文载入内把一个 80MB 的文全部入内并是一便便 的情!当们的程序需要处理一部数据时,量它处理较小的数据段 能够得到的性能升,因们程序拥有更多的资源来行相于 及大量磁盘读写的大块数据处理,使用谨慎的缓管理通常能够得更好的效果 文相关的内容参看第 2 一个有趣的文本来源是网通过 socket 网中回文本们把 socket 看是一个文使用 socket 象的 makefile 方法, socket 中回的数据能是完 整的一块,能是完整的数据块,时们继续等待直到更多的数据到达 由于在的数据块到达之前文本数据能是完整的,所用 makefile 创建的文 象能合被传递给文本处理码在处理来自网连接的文本时,在一 的处理之前,们通常需要完全读连接中的所有数据如果数据很庞大,们 文本 3 将入一个文,每当有新数据部到达,就在文断添加,直到接收 完所有数据,然将个文用于文本处理如果要求文本处理一要在接收完 所有数据之前启动,则需要在体的处理采用更加精的方法方面的解析器例 子参考标准中的 htmllib 和 HTMLParser 模块 符串基础 Python 供的用于文本处理的要的就是符串质改的符序列实 在种符串普通符串,包含了 8 ASCII符Unicode 符串,包 含了 Unicode 符们 Unicode 符串做多论它们的处理方法和普通 符串很类似,过它们每个符占用 2或者 4个节,所它们拥有千万 甚个的符,而普通符串仅有 256 个符当需要处理一有 母表的文本时,尤是洲的象形文,Unicode 符串的值就体出来了 而普通符串于英文及一非洲的精简语言经够用了比如,所有的欧洲 母表都用普通符串表示,们采的型的方法是使用标准编码ISO-8859-1 或者 ISO-8859-15,如果需要欧洲的币符号 在 Python 中,用列方式表一个文本符串 'this is a literal string' "this is another string" 符串的值被单引号或者引号圈种表示法在程序中完全一,都允许你在 符串内部把一种表示法的引用符号包括来,而无用斜线符号行转 'isn\'t that grand' "isn't that grand" 了一个文本符串扩展到多行,在一行的使用斜线符号,那意味着 面的一行是面符串的延续 big = "This is a long string\ that spans two lines." 如果想符串的输出行,在符串中嵌入换行符 big = "This is a long string\n\ that prints on two lines." 有一种方式是用一连续的引用符将符串圈 bigger = """ This is an even bigger string that spans three lines. """ 使用种引用符,无在文本中加入续行符和换行符,因文本将按照原貌被储 4 第 1 在 Python 的符串象中在符串前面加一个 r 或者 R,表示该符串是一 个真的原符串,需要它的原貌 big = r"This is a long string\ with a backslash and a newline in it" 使用原符串,斜线转被完全忽略了在符串前面加一个 u 或者 U 来使之一个 Unicode 符串 hello = u'Hello\u0020World' 符串是无法改的,那意味着无论你它行操作,你总是创建了一个新的 符串象,而是改了原有的符串符串是符的序列,所通过索引 的方法单个符 mystr = "my string" mystr[0] # 'm' mystr[-2] # 'n' 用的方法符串的一个部 mystr[1:4] # 'y s' mystr[3:] # 'string' mystr[-3:] # 'ing' 的方法扩展,增加第个参数,作的长 mystr[:3:-1] # 'gnirt' mystr[1::2] # 'ysrn' 通过循遍历整个符串 for c in mystr: 述方法将 c 依绑定到了 mystr 中的每一个符构建一个序列 list(mystr) # 返回 ['m','y',' ','s','t','r','i','n','g'] 通过简单的加法,实符串的拼接 mystr+'oid' # 'my stringoid' 乘法则完了符串多复 'xo'*3 # 'xoxoxo' 总之,符串做任何你能够他序列所做的操作,前是能试改符 序列,因符串是能改的 符串象有很多有用的方法比如,用 s.isdigit()来测试符串的内容,如果 s 是空的而且所有的符都是数,该方法将会返回 true则返回 false用 s.upper()来创建一个修改过的符串,该符串很像原符串 s,过中的每一个 符都是原符的大写形式用 haystack.count('needle')在一个符串中搜索一 文本 5 个符串,该方法返回了子串‘needle’在符串 haystack 中出的数如果有一个庞 大的包含多行文本的符串,用 splitlines 来将隔多个单行符串并置入一 个列表中 list_of_lines = one_large_string.splitlines( ) 然用 join 来新生一个庞大的单个符串 one_large_string = '\n'.join(list_of_lines) 本展示了符串象的许多方法在 Python 的 Library Reference 和 Python in a Nutshell 中读到更多的相关内容 Python 中的符串通过 re 模块用则表达式来操作则表达式是一种强大无 比但很复杂的,你能经通过他语言比如 Perl使用 vi 编辑器或 者使用命行方式的比如 grep它有所了解了在本的半中你会看到 一使用则表达式的例子更多相关文档,参考 Library Reference 和 Python in a Nutshell如果想掌握方面的知识,J.E.F. Friedl 的 Mastering Regular Expressions O’Reilly是值得荐的读本Python 的则表达式和 Perl 的则表达式基本相, Friedl 的书方面的内容绍得非常全面 Python 的标准模块 string 供的很多功能和符串方法供的功能基本相,者被打 包一系列函数,而非方法string 模块供一加的功能,比如 string.maketrans 函数,本中有几节展示了用法有一有用的符串常量比如,string.digits, 值’0123456789’,比如 Python 2.4 中引入的新类 Template,供了一种简单而灵活 的方式来格式化带有内嵌量的符串,你将在本中看到用符串格式化操作 符,%,供了一种简洁的方法将符串拼接并据某象比如浮点数来精确格 式化符串在本中你会看到大量相关例子并学会如何据自的目的来使用% Python 有一标准模块或者扩展模块,处理一定种类的符串本并覆盖 的殊领域,过本书第 12 将全面而深入地论 XML 处理方面的内容 1.1 每处理一个符 感谢Luther Blissett 任务 用每处理一个符的方式处理符串 解方案 创建一个列表,列表的子是符串的符意思是每个子是一个符串,长 度一Python 实并没有一个别的类型来符并和符串开 6 第 1 来们调用内建的 list,用符串作参数,如 thelist = list(thestring) 创建一个列表,而直接用 for 语完该符串的循遍历 for c in thestring: do_something_with(c) 或者用列表中的 for 来遍历 results = [do_something_with(c) for c in thestring] 或者,和列表效果完全一,用内建的 map 函数,每得一个符就调 用一处理函数 results = map(do_something, thestring) 论 在 Python 中,符就是长度 1 的符串循遍历一个符串,依它的 每个符用 map 来实差多的功能,要你愿意每到一个符就调用一 处理函数用内建的 list 类型来获得该符串的所有长度 1 的子串列表该 符串的符如果想获得的是该符串的所有符的合,调用 sets.Set,并 将该符串作参数在 Python 2.4 中,用的方式直接调用内建的 set import sets magic_chars = sets.Set('abracadabra') poppins_chars = sets.Set('supercalifragilisticexpialidocious') print ''.join(magic_chars & poppins_chars) # 集合的交集 acrd 更多资料 Library Reference 中关于序列的节Perl Cookbook,1.5 节 1.2 符和符值之间的转换 感谢Luther Blissett 任务 将一个符转化相的 ASCIIISO或者 Unicode 码,或者道而行之 解方案 是内建的函数 ord 和 chr 擅长的任务 >>> print ord('a') 97 >>> print chr(97) a 文本 7 内建函数 ord 接收长度 1 的 Unicode 符串作参数,时它返回一个 Unicode 的码值,大到 65535如果想把一个数的 Unicode 码值转化一个长度 1 的 Unicode 符串,用内建函数 unichr >>> print ord(u'\u2020') 8224 >>> print repr(unichr(8224)) u'\u2020' 论 是个很普通的任务,有时们需要将符在 Python 中是长度 1 的符串转换 ASCII 或者 Unicode 码,有时则道而行之,很常很有用内建的 ordchr 和 unichr 函数完全满足了相关的需求但注意,新手们常常混淆 chr(n)和 str(n)之间的别 >>> print repr(chr(97)) 'a' >>> print repr(str(97)) '97' chr 将一个小整数作参数并返回于 ASCII 单符的符串,而 str,能够任何整 数参数,返回一个该整数的文本形式的符串 如果想把一个符串转化一个包含各个符的值的列表,像面时使用 内建的 map 和 ord 函数 >>> print map(ord, 'ciao') [99, 105, 97, 111] 若想通过一个包含了符值的列表创建符串,使用".joinmap 和 chr比如 >>> print ''.join(map(chr, range(97, 100))) abc 更多资料 参 Library Reference 中的内建函数 chr,ord 和 unichr 有关节及 Python in a Nutshell 1.3 测试一个象是是类符串 感谢Luther Blissett 任务 有时候需要测试一个象,尤是当你在写一个函数或者方法的时候,经常需要测试传入的 参数是是一个符串或者更准确地说,个象是有类似于符串的行模式 8 第 1 解方案 面给出一个利用内建的 isinstance 和 basestring 来简单快地检查某个象是是 符串或者 Unicode 象的方法,如 def isAString(anobj): return isinstance(anobj, basestring) 论 很多遇到个题的程序员第一是行类型测试 def isExactlyAString(anobj): return type(anobj) is type('') 然而,种方法非常糟糕,因它破坏了 Python 强大力量的源泉质滑的基于 签的多态机制很明显 Unicode 象无法通过个测试,用户自编写的 str 的子 类行,甚任何一种行表类似于符串的用户自定类型的实例都无法通过 测试 本节荐的内建函数 isinstance 则要好很多内建类型 basestring 的在使得个方 法能basestring 是 str 和 unicode 类型的基类,任何类符串的用户自 定类型都该基类 basestring 派生,能保证 isinstance 的测试按照预期作 本 basestring 是一个空的类型,就像 object,所它派生子类并没有 开销 幸的是,个似乎完美的 isinstance 检查方案,于 Python 标准中的 UserString 模 块供的 UserString 类的实例,完全无能力而 UserString 象是非常明显的类符 串象,过它是 basestring 派生的如果想支持种类型,直接检查一个 象的行是真的像符串一,比如 def isStringLike(anobj): try: anobj + '' except: return False else: return True 个 isStringLike 函数比方案中给出的 isAString 函数慢且复杂得多,但它的确用于 UserString及他的类符串的类型的实例,用于 str 和 unicode Python 中通常的类型检查方法是所谓的鸭子判断法如果它走路像鸭子,声像鸭 子,那于们的用而言,就认它是鸭子了IsStringLike 函数过检查 了声部,那实够如果需要检查 anobj 象的更多的类符串,改 try 子,它检查更多节,比如 try: anobj.lower( ) + anobj + '' 据的经验,isStringLike 函数的测试通常就经满足需要了 文本 9 行类型验证或者任何验证任务的 Python 色的方法是据自的预期去执 行任务,在过程中检测并处理由于配产生的所有错误和常是一个著的 处理方式,做获得原谅总是比得到许要容易得多It's easier to ask forgiveness than permission,或简称 EAFPtry/except 是保证 EAFP 处理风格的关键 有时,像本节中的例子一,选择一个简单的判断方法,比如拼接一个空 符串,作一系列属性的合符串象供的各种操作和方法的一个 性判断 更多资料 参 Library Reference 中内建的 isinstance 和 basestring 的文档及 Python in a Nutshell 1.4 符串齐 感谢Luther Blissett 任务 实符串齐齐,居中齐,或者齐 解方案 是 string 象的 ljustrjust 和 center 方法要解的题每个方法都需要一个参数, 指出生的符串的宽度,之返回一个在端端或者端都添加了空格的 符串拷贝 >>> print '|', 'hej'.ljust(20), '|', 'hej'.rjust(20), '|', 'hej'.center(20), '|' | hej | hej | hej | 论 们常常能够碰到居中齐或齐的文本质比如,你能会打印一个简单的 告,并 monospace 体居中显示页码因种需求很常,Python 的 string 象供了个简单好用的方法在 Python 2.3 中,填充符能是空格在 Python 2.4 中,默认情况然使用空格,但是给种方法第个参数,指定一个填 充符 >>> print 'hej'.center(20, '+') ++++++++hej+++++++++ 更多资料 Library Reference 有关 string 的方法Java Cookbook,第 3.5 节 10 第 1 1.5 去除符串端的空格 感谢Luther Blissett 任务 获得一个开头和都没有多余空格的符串 解方案 符串象的 lstriprstrip 和 strip 方法是种任务而计的几个方法都 需要参数,它们会直接返回一个删除了开头或者端的空格的原符串 的拷贝 >>> x = ' hej ' >>> print '|', x.lstrip( ), '|', x.rstrip( ), '|', x.strip( ), '|' | hej | hej | hej | 论 有时候需要给符串添加一空格,符合预规定的固定宽度,完齐 或居中齐如前面 1.4 节所绍的,但有时需要端移除所有的空格空 制表符换行符等因种需求是如常,Python 的符串象给出了 3 个方法 来供种功能选择去除他符,需供一个符串作 3 种方法的 参数 >>> x = 'xyxxyy hejyx yyx' >>> print '|'+x.strip('xy')+'|' | hejyx | 注意,面例子中获得的符串的开头和结的空格都被保留来,因yx 面接着的是一空格有开头和结的x和y被真移除了 更多资料 Library Reference 中关于符串的方法第 1.4 节Java Cookbook 3.12 1.6 合并符串 感谢Luther Blissett 任务 有一小的符串,想把符串合并一个大符串 文本 11 解方案 要把一系列小符串连接一个大符串,使用符串操作符 join假如 pieces 是一个符串列表,想把列表中所有的符串按序拼接一个大符串, 做 largeString = ''.join(pieces) 如果想把储在一量中的符串段拼接来,那使用符串格式化操作符% 会更好一 largeString = '%s%s something %s yet more' % (small1, small2, small3) 论 Python 中,+操作符能够将符串拼接来,而实类似的功能假如有一保 在量中的符串段,使用面种码似乎是一种很自然的方式 largeString = small1 + small2 + ' something ' + small3 + ' yet more' 类似地,如果有一个小符串序列,假做 pieces,那很自然地,像编写 码 largeString = '' for piece in pieces: largeString += piece 或者,用完全等但更加漂亮和紧凑的方式 import operator largeString = reduce(operator.add, pieces, '') 过,要认述例子中给出的方法经足够好了,面给出的方法都有许多值得 敲的地方 Python 中的符串象是无法改的任何符串的操作,包括符串拼接,都将 产生一个新的符串象,而是修改原有的象因拼接 N 个符串将及创建 并丢 N-1 个中间结果当然,创建中间结果的操作会有更佳的性能,但能 一到地得结果 如果有少量符串尤是那绑定到量的需要拼接,甚有时需要添加一 额外的信息,Python 的符串格式化操作符%通常是更好的选择性能种操作 完全是一个题和使用多个+操作符相比,%操作符有一他的潜在优点一 旦惯了它,%会你的码的读性更好无所有的非符串如数 部调用 str,因格式指定符%s 经暗中做完了作一个优点是,使 用除%s 之外的他格式指定符,实更多的格式要求,比如将浮点数转化 符串的表示时,制它的有效数 12 第 1 是序列? Python 并没有一个特别的类型叫做 sequence,但序列是 Python 中一个非常常用的术 语。序列,严格地讲,是一个可以迭代的容器,可以从中取出一定数目的子项,也可 以一次取出一个,而且它还支持索引、切片,还可以传递给内建函数 len返回容器 中子项的数目。 Python 的 list 就是“序列”,你已经见过多次了,但还有很多其他的 “序列” string、unicode 对象、tuple、array.array 等。 通常,一个对象即使不支持索引、切片和 len。只要具有一次获得一项的迭代能力, 对应用而言就已经够用了。这叫做可迭代对象或者,如果我们把范围限定在拥有有 限子项的情况下,那就叫做有边界可迭代对象。可迭代对象不是序列,而是如字典 迭代操作将以任意顺序每次取得一个 key、文件对象迭代操作将给出文本文件的 行数等,还有其他一些,包括迭代器和生成器等。任何可迭代对象都能用在 for 循 环语句以及一些等价的环境中 Python 2.4 的生成器表达式、列表推导中的 for 子句、 以及很多内建的方法,比如 min、max、zip、sum、str.join 等。 在 http://www.python.org/moin/PythonGlossary,可以发现一个词汇表, Python Glossary, 它能够帮助你了解很多术语。虽然本书的编辑尽量严格按照那个词汇表的描述来使 用术语,仍可能发现本书在很多地方提到了序列、可迭代对象、甚至列表,实际上, 严格地讲,我们都应该指明为有边界的可迭代对象。比如,在本节的开头,我们说 “一个小字符串的序列”,实际上那是一个有边界的字符串的可迭代对象。在全书 使用“有边界的可迭代对象”这样的术语给人的感觉像是在读一本数学书,而不 是一本实践性很强的编程书!所以我们决定采用略微偏离严格术语系统的词,这 样有助于获得更好的可读性,同时也能够更好地体现本书的多元化。最后结果还 不错,根据上下文环境,那些不怎么严密的术语的真实含义仍然能够被清晰地表 达出来。 当一个序列中包含了很多的小符串的时候,性能就了一个很实的题在内 部使用了+或者+=和内建函数 reduce 作用相,但是更漂亮的循所需要的时间跟 需要累加的符数的方比,因配空间并填充一个大符串所需要的时间大 比于该符串的长度幸好 Python 供了一个更好的选择于符串象 s 的 join 方法,们传入一个符串序列作参数,它将返回一个由符串序列 中所有子符串拼接而的大符串,而且个过程中使用了一个 s 的拷贝用于串 接所有的子举个例子,".join(pieces)把 pieces 中所有的子一口吞,而无产生 子之间的中间结果,比如,', '.join(pieces)拼接了所有的子符串,并在邻接的 之间插入了一个逗号和空格是一种快整洁优且兼良好读性的合并 大符串的方法 但有时并是所有的数据在一开始就经就,比如数据能来自于输入或计算, 文本 13 时使用一个 list 作中间数据结构来容纳它们使用 list 的 append 或 extend 方法在添加新的数据在得了所有的数据之,调用".join(thelist)就得到 合并之的大符串在能教给你的 Python 的符串处理的各种和方法中, 是要的一条很多 Python 程序效能的原因是由于它们使用了+和+=来创建大 符串因,一定要醒自永要使用那种做法,而该使用本节荐的".join 方法 Python 2.4 在个题的改善做了很多作,在 Python 2.4 中使用+=,性能损失 要比前的本小一但".join 然要快许多,而且在各方面都更优势,少 于新人和心的开发人员来讲,它耗的时钟周期更少类似地,psyco一种 制的 just-in-time[JIT] Python 编译器,查看 http://psyco.sourceforge.net/能更大 幅度地减少+=带来的性能损失过,是要强调,’’.join 依然是值得选择的 方式 更多资料 Library Reference 和 Python in a Nutshell 中关于符串的方法符串格式化操作及 operator 模块的节 1.7 将符串符或词转 感谢Alex Martelli 任务 把符串符或词转过来 解方案 符串无法改,所,转一个符串需要创建一个拷贝简单的方法是使用一 种长-1 的别的方法,立产生一个完全转的效果 revchars = astring[::-1] 如果要按照单词来转符串,们需要创建一个单词的列表,将个列表转, 用 join 方法将合并,并在相邻词之间都插入一个空格 revwords = astring.split( ) # 字符串->单词列表 revwords.reverse( ) # 反转列表 revwords = ' '.join(revwords) # 单词列表 ->字符串 或者,如果喜简而紧凑的一行解的码 revwords = ' '.join(astring.split( )[::-1]) 14 第 1 如果想词转但又时改原的空格,用则表达式来隔原符串 import re revwords = re.split(r'(\s+)', astring) # 切割字符串为单词列表 revwords.reverse( ) # 反转列表 revwords = ''.join(revwords) # 单词列表 -> 字符串 注意,的 join 操作要使用空符串,因空格隔符经被保在 revwords 列 表中了通过 re.split,使用了一个带括弧的的则表达式当然,写一 行解的形式,要你乐意 revwords = ''.join(re.split(r'(\s+)', astring)[::-1]) 过显得过于紧凑,失去了读性,是好的 Python 码 论 在 Python 2.4 中,改写那个一行解的词转的码,使用新的内建函数 reversed 来原的读性略差的指示符[::-1] revwords = ' '.join(reversed(astring.split( ))) revwords = ''.join(reversed(re.split(r'(\s+)', astring))) 于符转,astring[::-1]然是好的方式,使在 Python 2.4 中,因如果要使 用 reversed,得调用".join revchars = ''.join(reversed(astring)) 新的内建函数 reversed 返回一个迭器iterator,该象被用于循或者传递给 他的累加器,比如".join,但它并是一个经完的符串 更多资料 Library Reference 和 Python in a Nutshell 中关于序列的和类型,及内建的 reversedPython 2.4的内容Perl Cookbook 1.6 1.8 检查符串中是包含某符合中的符 感谢Jürgen Hermann、Horst Hansen 任务 检查符串中是出了某符合中的符 解方案 简单的方法如,兼清晰快通用用于任何序列,仅仅是符串, 用于任何容器,仅仅是合 文本 15 def containsAny(seq, aset): """ 检查序列 seq 是否含有 aset 中的项 """ for c in seq: if c in aset: return True return False 使用更高和更复杂的基于标准 itertools 模块的方法来高一点性能,过它 们本实是一种方法 import itertools def containsAny(seq, aset): for item in itertools.ifilter(aset._ _contains_ _, seq): return True return False 论 于大多数及合的题,们好使用 Python 2.4 中引入的内建类型 set如果 在使用 Python 2.3,使用 Python 标准中的等的 sets.Set 类型然而,总是有 例外的情况比如,一个纯粹的基于合的方法该是像的 def containsAny(seq, aset): return bool(set(aset).intersection(seq)) 过种方法就意味着 seq 中的每个员都避免地要被检查而本节方法中给 出的函数,某种角度讲,被做短路法它一知道答案就返回如 果答案是 False,它必检查 seq 中的每个子质因除非检查所有的子,则 们无法确保 seq 中含有 aset 中的元素过如果答案是 True,们通常很 快知道,因要到一个子是 aset 的员就了当然通常是依赖于数据 的如果 seq 很短或者答案是 False,实并没有多大别,但如果 seq 很长,用 哪种方式来检查就得极关键了尤是答案是 True 而且又很快探知结果的 情况 本节给出的 containsAny 的第一个本有着简洁和清晰的优点它直地表达了它背 蕴藏的思想而第个本则能显得有点聪明,个词在 Python 的世界中 是一个面的表达赞美的形容词,因简洁和清晰才是个世界的心值 过,第个本有值得思考的地方,因它展示了一种高的基于标准的 itertools 模块的方法大多数情况高方法总是比方法更好当然在本节的 个别的例子中,情况好相itertools.ifilter要求传入一个断定译者注Predicate, 原意断言谓词或述词和一个迭象,然筛选出迭象中的满足该 断定的述的所有子,所谓的断定,们用的是 anyset._ _contains_ _, 当们编写 in anyset 的码来做检查的时候,内部调用的就是 anyset._ _contains_ _ 所,如果 ifilter 到了东西,比如它出一个 seq 的子,时是 anyset 的员,函数就会立刻返回 True如果程序行到了 for 语之,那肯定表示 return 16 第 1 True 本没有被执行,因 seq 中的任何子都是 anyset 的员,时能返 回 False 是断定? 在一些编程的讨论中你常常可以看到这个词 predicate意为一个返回 True 或 False 的函数或者其他可调用对象。如果它返回 True,我们就称这个断定成立。 如果你的程序需要用到像 containsAny 的函数来检查一个符串或他序列是 包含了某个合的员,能会写出的种 def containsOnly(seq, aset): """ 检查序列 seq 是否含有 aset 中的项 """ for c in seq: if c not in aset: return False return True containsOnly 和 containsAny 完全一,过好辑颠倒了一面有一个类 似的例子,完全没办法短路必检查所有的子,们好使用于内建的 set 类型 Python 2.4或 Python 2.3 中的 sets.Set,使用方法一 def containsAll(seq, aset): """ 检查序列 seq 是否含有 aset 的所有的项 """ return not set(aset).difference(seq) 如果惯用 set或 sets.Set的方法 difference,记它的语任何一个 set 象 a,a.difference(b)就像 a-set(b)返回 a 中所有属于 b 的元素比如 >>> L1 = [1, 2, 3, 3] >>> L2 = [1, 2, 3, 4] >>> set(L1).difference(L2) set([ ]) >>> set(L2).difference(L1) set([4]) 希望的结果解释得更清楚 >>> containsAll(L1, L2) False >>> containsAll(L2, L1) True 一方面,要把 difference 和 set 的他方法搞混了,比如 symmetric_difference,它 返回的合包含了所有属于中一个合且属于一个合的元素 如果需要处理 seq 和 aset 中的符串非 Unicode,能需要本节中通用的函 数,而尝试更加殊的方式,如第 1.10 节中的方法,基于符串的方法 translate 和 Python 标准中的 string.maketrans 函数 文本 17 import string notrans = string.maketrans('', '') # identity "translation" def containsAny(astr, strset): return len(strset) != len(strset.translate(notrans, astr)) def containsAll(astr, strset): return not strset.translate(notrans, astr) 看来有点的方法要依赖于个实strset.translate(notrans, astr)是 strset 的子序列,而且是由所有属于 astr 的符的如果个子序列和 strset 有的 长度,说明 strset.translate 没有删除任何符,因 strset 中任何一个符都属于 astr相,如果子序列是空,说明所有的符都被移除了,所所有属于 strset 的 符属于 astr当程序员们把符串当做符合的时候,很自然地就会想到使用 translate 办法,因个方法度错,而且灵活易用,更多节参看第 1.10 节 过本节中的种方式的通用性完全早出的方式通用性非常好并局限 于符串处理,它你要处理的象类型的要求更少而基于 translate 方法的方式 则相,它要求 astr 和 strset 都是普通符串,或者在行和功能要普通符串非 常相似甚连 Unicode 符串都行,因 Unicode 符串的 translate 方法的签 于普通符串的 translate 本质Unicode 本需要一个参数该参数是一 个把码值映射到 Unicode 符串或者 None 的 dict 象,普通符串本则需要个 必都是普通符串 更多资料 1.10 节Library Reference 和 Python in a Nutshell 中关于普通符串和 Unicode 符串 的 translate 方法的文档,string 模块的 maketrans 函数的内容阅读材料中的内建 set 象,sets 和 itertools 模块,及殊的_ _contains_ _方法相关内容 1.9 简化符串的 translate 方法的使用 感谢Chris Perkins、Raymond Hettinger 任务 用符串的 translate 方法来行快编码,但发很难记个方法和 string.maketrans 函数的用节,所需要它们做个简单的封装,简化使用流程 解方案 符串的 translate 方法非常强大而灵活,体节参考第 1.10 节因它的威力 和灵活性,将它包装来简化用就了个好意一个返回包的厂函数 很好地完种任务 18 第 1 import string def translator(frm='', to='', delete='', keep=None): if len(to) == 1: to = to * len(frm) trans = string.maketrans(frm, to) if keep is not None: allchars = string.maketrans('', '') delete = allchars.translate(allchars, keep.translate(allchars, delete)) def translate(s): return s.translate(trans, delete) return translate 论 经常发有使用符串的 translate 方法的需求,但每都得停来回想它的用 法节第 1.10 节供的更多节信息所,脆给自写了个类来改写 了本节中展示的厂包的形式,把各种能性封在一个简单易用的接口面 在,如果需要一个函数来选出属于指定合的符,就简单地创建并使用它 >>> digits_only = translator(keep=string.digits) >>> digits_only('Chris Perkins : 224-7992') '2247992' 移除属于某符合的元素简单 >>> no_digits = translator(delete=string.digits) >>> no_digits('Chris Perkins : 224-7992') 'Chris Perkins : -' 甚,用某个符换属于某指定合的符 >>> digits_to_hash = translator(from=string.digits, to='#') >>> digits_to_hash('Chris Perkins : 224-7992') 'Chris Perkins : ###-####' 虽然面那个用显得有点殊,但然时地碰到有种需求的任务 当然,的计有点断,当 delete 参数和 keep 参数有叠部的时候, delete 参数优 >>> trans = translator(delete='abcd', keep='cdef') >>> trans('abcdefg') 'ef' 于你的程序,如果 keep 被指定了,能忽略掉 delete 会更好一,或者,如果 者都被指定了,抛出个常错,因在一个 translator 的调用中时指定 者能没意外,和第 1.8 节和第 1.10 节相似,本节码用于普通符 串, Unicode 符串并用参看第 1.10 节,了解到怎 Unicode 符串 文本 19 编写类似功能的码,并看到 Unicode 的 translate 方法普通单节符串的 translate 的别 包 闭包 closure不是什么复杂得不得了的东西它只不过是个“内层”的函数,由一 个名字变量来指代,而这个名字变量对于“外层”包含它的函数而言,是本 地变量。我们用一个教科书般的例子来说明 def make_adder(addend): def adder(augend): return augend+addend re turn adder 执行 p = make_addr(23)将产生内层函数 adder 的一个闭包,这个闭包在内部引用了名 字 addend,而 addend 又绑定到数值 23。q = make_adder(42)又产生另一个闭包,这次 名字 addend 则绑定到了值 42。q 和 p 相互之间并无关联,因此它们可以相互独立地 和谐共存。现在我们就可以执行它们了,比如, print p(100), q(100)将打印出 123 142。 实际上,我们一般认为 make_adder 指向一个闭包,而不是说什么迂腐拗口的“一个 返回闭包的函数” —幸运的是,根据上下文环境,通常这样也不至于造成误解。称 make_adder 为一个工厂或者工厂函数也是简洁明确的还可以称它为一个闭包 工厂来强调它创建并返回闭包,而不是返回类或者类的实例。 更多资料 参看第 1.10 节中关于本节 translator(keep=…)的一个等实,及该节 translate 方 法的更多述,有Unicode符串的方案Library Reference和Python in a Nutshell 中的符串的 translate 方法的文档,string 模块的 maketrans 函数的相关内容 1.10 过滤符串中属于指定合的符 感谢Jürgen Hermann、Nick Perkins、Peter Cogolo 任务 给定一个需要保留的符的合,构建一个过滤函数,并将用于任何符串 s, 函数返回一个 s 的拷贝,该拷贝包含指定符合中的元素 解方案 于类题,string 象的 translate 方法是又快又好用的过,了有效地使 用 translate 来解题,们必做一准备作传递给 translate 的第一个参数 是一个翻译表在本节中,们实需要翻译,所们必准备一个制的 20 第 1 参数来指明无翻译第个参数指出了们需要删除的符个任务要求们 保留好过来,是删除属于某符合的符,所们必该符合 准备一个补,作第个参数质就删除所有们想保留的符包 是一性完所有准备作的好方法,它能够返回一个满足需求的快过滤函数 import string # 生成所有字符的可复用的字符串,它还可以作为 # 一个翻译表,指明“无须翻译” allchars = string.maketrans('', '') def makefilter(keep): """ 返回一个函数,此返回函数接受一个字符串为参数 并返回字符串的一个部分拷贝,此拷贝只包含在 keep 中的字符,注意 keep 必须是一个普通字符串 """ # 生成一个由所有不在 keep 中的字符组成的字符串: keep 的 # 补集,即所有我们需要删除的字符 delchars = allchars.translate(allchars, keep) # 生成并返回需要的过滤函数作为闭包 def thefilter(s): return s.translate(allchars, delchars) return thefilter if _ _name_ _ == '_ _main_ _': just_vowels = makefilter('aeiouy') print just_vowels('four score and seven years ago') # 输出:ouoeaeeyeaao print just_vowels('tiger, tiger burning bright') # 输出:ieieuii 论 理解本节的关键在于 Python 标准的 string 模块的 maketrans 函数及符 串象的 translate 方法的理解translate 用于一个符串并返回该符串的一个 拷贝,个拷贝中的所有符都将按照传入的第一个参数翻译表指定的换方 式来换,而且,第个参数指定的所有符都将被删除maketrans 是创建翻译表 的一个函数翻译表是一个好有 256 个符的符串 t当你把 t 作第一 个参数传递给 translate 方法时,原符串中的每一个符 c,在处理之都被翻译 了符 t[ord(c)] 在本节的中,们将整个过滤任务解准备段和执行段,使效能达到了 大化由于包含所有符的符串需要复使用,们创建它一,并在模块入 将它置全局量采用种方式的原因是们确定每个过滤函数都使用的 翻译表,因它非常节省内而们要传递给 translate 的第个参数质需要删除 的符,则依赖于需要保留的符合,因它完全是者的补们需要通知 translate 删除们想保留的符所,们用 makefilter 厂函数来创建需要删除 文本 21 的符合符串,通过使用 translate 方法来删除需要保留的符,一很 快得完和本节的他函数一,无论是创建是执行,translate 的度都非常 快程序中的执行部给出的测试码,则展示了如何通过调用 makefilter 构建一个过 滤函数,并给个过滤函数绑定一个需简单地给 makefilter 的返回结果指定个 ,然一测试符串调用该函数并打印出结果 带一,用 allchars 作参数调用过滤函数会把所有需要保留的符处理一种非 常规整的符串质格按照母表排序而且没有复的符据种思路编 写一个很简单的函数,将符串形式给出的符合处理规整的形式 def canonicform(s): """ 给定字符串 s,将 s 的字符以一种规整的字符串形式返回: 按照字母顺序排列且没有重复 """ return makefilter(s)(allchars) 在解方案小节中给出的码,使用了 def 语来建立一个嵌套的函数包, 是因 def 是常通用,是清晰的创建函数的语过如果你乐意, 用 lambda 来原来的语,需修改 makefilter 函数中的 def 和 return 语,然 写需一行的 return lambda 语 return lambda s: s.translate(allchars, delchars) 大多数 Python 玩家认,相于 lambda,def 更清晰且更读性 既然本节处理的一符串被看作符合,用 sets.Set 类型或 Python 2.4 中的内建 set 类型来完相的任务但得益于 translate 方法的威力和度, 在类似的种直接处理符串的任务中,使用 translate 总是比通过 set 来实要快 一过,如第 1.8 节到的,本节中给出的用于普通符串, Unicode 符串则用 了能够解 Unicode 符串的题,们需要做完全的准备作Unicode 符串的 translate 方法需要一个参数一个序列或者映射,并且据符串中的每个 符的码值行索引码值是映射的键或者序列的索引值的符会被直接复制, 做改每个符码的值必是一个 Unicode 符串该符的换物或 者 None意味着该符需要被删除种用法看去既优又强大,惜于普 通符串用,所们得写码 通常,们使用 dict 或 list 作 Unicode 符串的 translate 方法的参数,来翻译或者删 除某符但由于本节任务有殊保留一符,删掉所有余符,们 能会需要一个非常庞大的 dict 或 string质但仅仅是把所有的他符映射到 None 更好的办法是,编写一个简单的大实了_ _getitem_ _行索引操作时会调用的 殊方法方法的类一旦们花点功完了个小类,们个类的实例 被调用,直接给个类个 makefilter 的别 22 第 1 import sets class Keeper(object): def _ _init_ _(self, keep): self.keep = sets.Set(map(ord, keep)) def _ _getitem_ _(self, n): if n not in self.keep: return None return unichr(n) def _ _call_ _(self, s): return unicode(s).translate(self) makefilter = Keeper if _ _name_ _== '_ _main_ _': just_vowels = makefilter('aeiouy') print just_vowels(u'four score and seven years ago') # 输出:ouoeaeeyeaao print just_vowels(u'tiger, tiger burning bright') # 输出:ieieuii 们直接就把个类命 makefilter,但是,基于传统,一个类的通常 该首母大写遵循传统一般来说没有害处,所,码就了个子 更多资料 第 1.8 节Library Reference 和 Python in a Nutshell 中普通符串和 Unicode 符串的 translate 方法的有关内容,及 string 模块的 maketrans 函数的绍 1.11 检查一个符串是文本是制 感谢Andrew Dalke 任务 在 Python 中,普通符串既容纳文本,容纳任意的节,在需要探知当 然,完全是启发式的试探于个题并没有精准的算法一个符串中的数 据是文本是制 解方案 们采 Perl 的判定方法,如果符串中包含了空值或者中有超过 30%的符的高 被置 1意味着该符的码值大于 126或是奇怪的制码,们就认段数据是 制数据们得自编写码,优点是于殊的程序需求,们时调 整种启发式的探知方式 from _ _future_ _ import division # 确保/不会截断 import string text_characters = "".join(map(chr, range(32, 127))) + "\n\r\t\b" 文本 23 _null_trans = string.maketrans("", "") def istext(s, text_characters=text_characters, threshold=0.30): # 若 s 包含了空值,它不是文本 if "\0" in s: return False # 一个“空”字符串是“文本”这是一个主观但又很合理的选择 if not s: return True # 获得 s 的由非文本字符构成的子串 t = s.translate(_null_trans, text_characters) # 如果不超过 30%的字符是非文本字符, s 是字符串 return len(t)/len(s) <= threshold 论 轻易地修改函数 istext 的启发式探知部,需传递一个指定的阀值作判断某 符串所含数据是文本常的 ASCII 符加 4 个常的制码,在文本中 几个制码都是有意的的基准,默认的阀值是 0.3030%举个例子,如果期 望它是采用了 iso-8859-1 的意大利文本,给 text_characters 参数添加意大利语中的 一音母,底粗粘亚ò功 很多时候,需要检查的象是文,而是符串,就是说要判断文中的内容是 文本是制数据地,们采用 Perl 的启发式方法,用前面供的 istext 函数来检查文的第一个数据块 def istextfile(filename, blocksize=512, **kwds): return istext(open(filename).read(blocksize), **kwds) 注意,默认情况,istext 函数中的 len(t)/len(s)将被截断 0,因是一个整数之间 的除法结果的本计是 Python 3.0,几发布,Python 中的/操作符的意 会被改,们在做除法算的时候就会发生截断质如果你确实需要截断, 用截断除法操作符// 过,在 Python 没有改除法的语,是了保证一定的向兼容性了 千万行的有的 Python 程序和模块滑地作于所有的 Python 2.x 本,非常 要过,于语言本的本号的改,Python 允许行考虑向兼容性的 改 因,于本节的解方案中的模块,按照来本中计划的行模式来改除法的 行是非常方便的,们用种方式来引入模块 from _ _future_ _ import division 条语并影响程序的余部,影响紧声明的模块通过个模块,/表 得像真实的除法没有截断于 Python 2.3 和 2.4,dvision 能是唯一需要 _ _future_ _入的模块他的一来本中计划的性,nested_scope 和生 24 第 1 器,在经是语言的一部了,因而无法被关质当然明确入它们没有坏 处,但有在你的程序需要能够行在老本的 Python 境时,种做法才有意 更多资料 1.10 节中关于 maketrans 函数及符串方法 translate 的多节Language Reference 中关于真实除法和截断除法的内容 1.12 制大小写 感谢Luther Blissett 任务 将一个符串由大写转小写,或者道而行之 解方案 是符串象供 upper 和 lower 方法的原因每个方法都需要参数,直接返回 一个符串的拷贝,中的每个母都被改大写形式质或小写形式 big = little.upper( ) little = big.lower( ) 非母的符按照原被复制 s.capitalize 和 s[:1].upper()+s[1:].lower()相似第一个符被改大写,余符被转 小写s.title 很相似,过它将每个单词的第一个母大写的单词是母 的序列,余部则转小写 >>> print 'one tWo thrEe'.capitalize( ) One two three >>> print 'one tWo thrEe'.title( ) One Two Three 论 操作符串大小写是很常的需求,有很多方法你创建需要的符串外, 检查一个符串是经是满足需求的形式,比如 isupperislower 和 istitle 方 法,如果给定的符串是空的,少含有一个母,而且别满足全部大写全部 小写每个单词开头大写的条,种方法都会返回一个 True,但是没有类似的 iscapitalized 方法过如果们需要一个行方式类似于is…的方法,自编写 码很简单如果给定的符串是空的,那方法都会返回 False如果给定的符 串非空,但是包含任何母符,将全部返回 False 清楚简单的 iscapitalized,仅需简洁的一行 文本 25 def iscapitalized(s): return s == s.capitalize( ) 过,偏离了is…方法们的行模式,于空符串和含母的符串,它 返回 True们给出一个格点的本 import string notrans = string.maketrans('', '') #identity''translation'' def containsAny(str, strset): return len(strset) != len(strset.translate(notrans, str)) def iscapitalized(s): return s == s.capitalize( ) and containsAny(s, string.letters) ,们用了第 1.8 节中的函数来确保,当遇到了空符串或含母的符串,返 回值是 False过如第 1.8 节的示一,那意味着个别的 iscapitalized 用于普通符串, Unicode 符串用 更多资料 Library Reference 和 Python in a Nutshell 中关于符串方法的绍Perl Cookbook 1.9 1.8 节 1.13 子符串 感谢Alex Martelli 任务 获符串的某个部比如,你读了一条定长的记录,但想获条记录中的 某段的数据 解方案 是个好方法,但是它一能得一个段 afield = theline[3:8] 如果需考虑段的长度,struct.unpack 能更合比如 import struct # 得到一个 5 字节的字符串,跳过 3 字节,得到两个 8 字节字符串,以及其余部分: baseformat = "5s 3x 8s 8s" # theline 超出的长度也由这个 base-format 确定 #在本例中是 24 字节,但 struct.calcsize 是很通用的 numremain = len(theline) - struct.calcsize(baseformat) # 用合适的 s 或 x 字段完成格式,然后 unpack format = "%s %ds" % (baseformat, numremain) l, s1, s2, t = struct.unpack(format, theline) 26 第 1 如果想跳过余部,需要给出确的长度,解出 theline 的开头部的数据 l, s1, s2 = struct.unpack(baseformat, theline[:struct.calcsize(baseformat)]) 如果需要获 5 节一的数据,利用带列表LC的方法,码很 简单 fivers = [theline[k:k+5] for k in xrange(0, len(theline), 5)] 将符一个个单独的符更加容易 chars = list(theline) 如果想把数据指定长度的列,用带 LC 的方法通常是简单的 cuts = [8, 14, 20, 26, 30] pieces = [ theline[i:j] for i, j in zip([0]+cuts, cuts+[None]) ] 在 LC 中调用 zip,返回的是一个列表,中每都是形如(cuts[k], cuts[k+1])的数 ,除了第一和一,别是(0, cuts[0])和(cuts[len(cuts)-1], None)换 话说,每一个数都给出了用于割的确的(i, j),仅有第一和一例外,前者 给出的是割之前的方式,者给出的是割完之到符串的剩余部 LC 利用数就确地将 theline 开来 论 本节到了 Perl Cookbook 1.1 的启发Python 的方法,了 Perl 的 substr Perl 的内建的 unpack 和 Python 的 struct.unpack 非常相似过 Perl 的手段更丰富一 点,它用*来指定一个段长度,并指剩余部在 Python 中,无论是了 获或者跳过某数据,们都得计算和插入确的长度过是大题, 因类抽段数据的任务被封装小函数如果该函数需要复被调用的 话,memoizing,通常被称自动缓机制,能够极大地高性能,因它避免了 struct.unpack 复做一格式准备作参第 18.5 节中关于 memoizing 的更多节 在纯 Python 的境中,struct.unpack 作符串的一种方案,非常好用当 然能和 Perl 的 substr 比,虽然它接用*指定的域长度,但是值得荐的好 东西 码段,好被封装函数封装的一个优点是,们需要每使用时都计 算一个域的长度面的函数基本等于解方案小节给出的直接使用 struct.unpack 的码段 def fields(baseformat, theline, lastfield=False): # theline 超出的长度也由这个 base-format 确定 #通过 struct.calcsize 计算确切的长度 numremain = len(theline)-struct.calcsize(baseformat) 文本 27 # 用合适的 s 或 x 字段完成格式,然后 unpack format = "%s %d%s" % (baseformat, numremain, lastfield and "s" or "x") return struct.unpack(format, theline) 一个值得注意或者说值得批评的计是该函数供了 lastfield=False 一个 选参数基于一个经验,虽然们常常需要跳过的长度知的部,有时候 们是需要获那段数据采用 lastfield and s or x等于 C 语言中的元算 符,lastfield?"s":"c"的表达式,们省去了一个 if/else,过是需要点 紧凑牲读性有值得商榷之处参看第 18.9 节中有关在 Python 中模拟元 算符的内容 若 fields 函数在一个循内部被调用,使用元(baseformat, len(theline), lastfield)作 key 来充利用 memoizing 机制将极大地高性能给出一个使用 memoizing 机 制的 fields 本 def fields(baseformat, theline, lastfield=False, _cache={ }): # 生成键并尝试获得缓存的格式字符串 key = baseformat, len(theline), lastfield format = _cache.get(key) if format is None: # 没有缓存的格式字符串,创建并缓存之 numremain = len(theline)-struct.calcsize(baseformat) _cache[key] = format = "%s %d%s" % ( baseformat, numremain, lastfield and "s" or "x") return struct.unpack(format, theline) 种利用缓的方法,目的是将比较耗时的格式准备作一完,并储在_cache 中当然,像所有的优化措施一,种采用了缓机制的优化需要通过测 试来确定能在多大程度高性能个例子,的测试结果是,通过缓优 化的本要比优化之前快 30%到 40%,换话说,如果个函数是你的程序的性 能瓶颈部,实没有必要多一举 解方案中给出的一个关于 LC 的码段,封装函数 def split_by(theline, n, lastfield=False): # 切割所有需要的片段 pieces = [theline[k:k+n] for k in xrange(0, len(theline), n)] # 若最后一段太短或不需要,丢弃之 if not lastfield and len(pieces[-1]) < n: pieces.pop( ) return pieces 一个码段的封装 def split_at(theline, cuts, lastfield=False): #切割所有需要的片段 pieces = [ theline[i:j] for i j in zip([0]+cuts, cuts+[None]) ] 28 第 1 # 若不需要最后一段,丢弃之 if not lastfield: pieces.pop( ) return pieces 在面例子中,利用列表来要比用 struct.unpack 略好一 用生器实一个完全的方式,像 def split_at(the_line, cuts, lastfield=False): last = 0 for cut in cuts: yield the_line[last:cut] last = cut if lastfield: yield the_line[last:] def split_by(the_line, n, lastfield=False): return split_at(the_line, xrange(n, len(the_line), n), lastfield) 当需要循遍历获的结果序列时,无论是显式调用,是借助一调用的累加 器,比如’’.join 来行式调用,基于生器的方式都会更加合如果需要的是各 段数据的列表,你手得到的结果是一个生器,调用内建的 list 来完转 化,像 list_ f_fields = list(split_by(the_line, 5)) o 更多资料 第 18.9 节和第 18.5 节Perl Cookbook 1.1 1.14 改多行文本符串的缩 感谢Tom Good 任务 有个包含多行文本的符串,需要创建该符串的一个拷贝,并在每行行首添加或者 删除一空格,保证每行的缩都是指定数目的空格数 解方案 符串象经供了趁手的,们需写个简单的函数满足需求 def reindent(s, numSpaces): leading_space = numSpaces * ' ' lines = [ leading_space + line.strip( ) for line in s.splitlines( ) ] return '\n'.join(lines) 文本 29 论 处理文本的时候,们常常需要改一块文本的缩解方案给出的码,在多 行文本的每行行首增减了空格,每行开头都有相的空格数比如 >>> x = """ line one ... line two ... and line three ... """ >>> print x line one line two and line three >>> print reindent(x, 4) line one line two and line three 使每行的缩都截然,该函数能够使它们的缩得完全一,有时是 们所需要的,但有时是一个常的需求是,调整每行行首的空格数,并确保 整块文本的行之间的相缩发生化无论是向是向调整,都是难 过,向调整需要检查一每行行首的空格,确保会把非空格符截去因, 们需要将个任务解,用个函数来完转化,加一个计算每行行首空格并 返回一个列表的函数 def addSpaces(s, numAdd): white = " "*numAdd return white + white.join(s.splitlines(True)) def numSpaces(s): return [len(line)-len(line.lstrip( )) for line in s.splitlines( )] def delSpaces(s, numDel): if numDel > min(numSpaces(s)): raise ValueError, "removing more spaces than there are!" return '\n'.join([ line[numDel:] for line in s.splitlines( ) ]) 所有函数都依赖符串的方法 splitlines,它和据’\n’来的 split 很相似过 splitlines 有额外的好处,它保留了每行的换行符当你传入的参数是 True 的时 候有时非常方便如果 splitlines 个符串方法没有供个能力,addSpaces 能短小精悍 然,们用函数合一个函数来删除行首空格该函数在保持各行之 间的相缩的情况,删除它能够删除的空格,缩小的行端边界 齐 def unIndentBlock(s): return delSpaces(s, min(numSpaces(s))) 30 第 1 更多资料 Library Reference 和 Python in a Nutshell 中关于序列类型的部 1.15 扩展和压缩制表符 感谢Alex Martelli、David Ascher 任务 将符串中的制表符转化一定数目的空格,或者道而行之 解方案 将制表符转换一定数目的空格是一种很常的需求,用 Python 的符串供的 expandtabs 方法轻松解题由于符串能被改,个方法返回的是一个 新的符串象,是原符串的一个修改过的拷贝过,将修改过的拷贝绑 定到原符串的 mystring = mystring.expandtabs( ) 并会改 mystring 原指向的符串象,过将 mystring 绑定到了一 个新创建的修改过的符串拷贝了,该符串拷贝中的制表符经被扩展一 空格了 expandtabs 来说,默认情况,制表符的宽度 8给 expandtabs 传递 一个整数参数来指定新的制表符宽度 将空格转制表符则比较少和怪如果真的想要压缩制表符,好是用别的办 法来解,因 Python 没有供一个内建的方法来扩展空格,将转化制表 符当然,们自写个函数来完任务符串的处理及新拼接, 比整个符串复转换要快得多 def unexpand(astring, tablen=8): import re # 切分成空格和非空格的序列 pieces = re.split(r'( +)', astring.expandtabs(tablen)) # 记录目前的字符串总长度 lensofar = 0 for i, piece in enumerate(pieces): thislen = len(piece) lensofar += thislen if piece.isspace( ): # 将各个空格序列改成 tabs+spaces numblanks = lensofar % tablen numtabs = (thislen-numblanks+tablen-1)/tablen pieces[i] = '\t'*numtabs + ' '*numblanks return ''.join(pieces) 文本 31 例子中的 unexpand 函数,用于单行符串要处理多行符串,用’’. Join ([ unexpand(s) for s in astring.splitlines(True) ]) 论 虽然在 Python 的符串操作中,则表达式来是必少的部,但有时它真的 很方便如码所示,unexpand 函数利用了 re.split 相于符串的 split 额外供的 性当则表达式包含了一个括弧时,re.split 返回了一个 list 列表,列表中的每 个相邻的隔段之间都被插入了一个用于隔的隔器,们就得到了 一个 pieces 列表,所有的连续空符串和非空符串都了它的子接着 们在 for 循中持续跟踪处理符串的长度,并将所有的空符段能地 转换相的制表符,加必要的剩余空格保持总体的长度 于很多编程任务来说,扩展制表符并是简简单单地调用 expandtabs 方法比如, 需要整理一 Python 源码,码写得是很规范,用空格来制缩 用空格是好的方式,而是采了制表符和空格的混合方式是非常糟糕的做法 实加剧了复杂性,比如,需要猜测制表符的宽度采用标准的 4 个空格的缩 方式是值得强烈荐的外,你能需要保留一在符串内部的并非用于制 缩的制表符有人能错误地使用了实的制表符,而是\t,来指符串中 间的制表符,甚你能需要有意的文本做的处理在某情况 ,题算棘手,比如,假需要处理每行文本行首的空符,而无理会 他的制表符一个像面的用则表达式的小函数就足够了 def expand_at_linestart(P, tablen=8): import re def exp(mo): return mo.group( ).expand(tablen) eturn ''.join([ re.sub(r'^\s+', exp, s) for s in P.splitlines(True) ]) r expand_at_linestart 函数充利用了 re.sub 函数,re.sub 在一个符串中搜符合 则表达式述的段,每当它到一个配,便将配的符串作一个参数传 递给一个函数,并调用该函数返回一个换配符串的符串了方便, expand_at_linestart 被计处理多行文本符串 P,并调用 splitlines 的结果使 用了列表处理,’\n’.join 将完的各行符串拼接来当然,的计 完全用于单行文本符串 实的制表符扩展的任务能会很殊,比如需要考虑制表符是在一个符串的中间 是外部,是处于哪种类型的文本中比如,源码中的注释文本和码文本,但 管怎,少都需要做一个断词的作外,能需要待处理的源码行 一个完全的解析,而是简单地依赖一符串和则表达式操作如果你想完的 任务有种要求,作量会相当仔阅读第 16 ,那一的内容初 学者有很大帮助 32 第 1 当你汗流浃背地完了转化任务,一定能深深体会到,管是写码是编辑 码,一定要遵循那种常用的被荐的 Python 编码风格使用空格4 个空格缩 一用制表符在符串中间的制表符该用\t,而是实的制表符你喜 爱的 Python 编辑器该被强化支持使用惯,在保 Python 源码文时 你能得到一个遵定的格式比如,IDLEPython 所带的免费的开发境 带的编辑器就完全支持定好能够在题出之前就配置好你的编辑器,而 是在题出之采弥补措施 更多文档 Library Reference 的序列类型一节的符串 expandtabs 方法Perl Cookbook 1.7 Library Reference 和 Python in a Nutshell 文档中的 re 模块 1.16 换符串中的子串 感谢Scott David Daniels 任务 需要一个简单的方法来完一个任务给定一个符串,通过查一个换, 将符串中被标记的子符串换掉 解方案 面给出的解办法既用于 Python 2.3,用于 2.4 def expand(format, d, marker='"', safe=False): if safe: def lookup(w): return d.get(w, w.join(marker*2)) else: def lookup(w): return d[w] parts = format.split(marker) parts[1::2] = map(lookup, parts[1::2]) return ''.join(parts) if _ _name_ _ == '_ _main_ _': print expand('just "a" test', {'a': 'one'}) # 输出:just one test 如果参数 safe 是 False,则默认条,符串中所有被标记的子符串必能够在 d 中到,则,expand 会抛出一个 KeyError 常并执行当参数 safe 被明确指定 True 时,如果被标记的子符串在中到,则被标记的部会被改 论 expand 函数码的体部有个很有趣的地方据操作是被要求全,它使用 文本 33 个的嵌套函数者有着的 lookup中的一个全意味着被标记的 子符串该能够在查到,如果查到,抛出 KeyError 常如果个函数并 是必全的默认情况全,lookup 据索引 d,并在该索引子 符串在的时候抛出个错误但如果 lookup 被要求全的,它将使用 d 的方 法 get,get 返回据索引能够查到的值,若到就返回在边加了标记的被查 的子符串给 safe 传入 True,表明你宁看到输出中有标记符愿看到常信息 marker+w+marker 是换 w.join(marker*2)的一种方式,但采用者的原因是, 它展示了一种简明很有意思的构带引号符串的方法 管用哪个本的 lookup,expand 都会执行修改拼接质在 Python 的 符串处理中要的操作在 expand 中行修改的部,使用了指定了长的列表 方法确地说,expand 并新绑定了 parts 的奇数索引的,因好 是原符串中于个标记符之间的部因,它们就是被标记的子符串,就 是需要在中查的符串 本节解方案给出的 expand 函数接非常灵活的符串语法形式,比基于$的 string.Template 更灵活你如果想输出符串包括引号,指定他的标记 符当然,函数没有限制被标记的子串能是标识符,轻松地插入 Python 表达 式d 的_ _getitem_ _方法会执行 eval 操作或者任意他占符而且,轻易 地搞出点有的更有趣的效果,比如 print expand('just "a" ""little"" test', {'a' : 'one', '' : '"'}) 输出结果是 just one "little" test高用户定制 Python 2.4 的 string.Template 类,通 过继来实述的所有功能,甚更多他高功能但本节解方案中的小的 expand 函数更加简洁易用 更多文档 Library Reference 关于 string.TemplatePython 2.4序列类型关于符串的 splitjoin 方法及操作及关于索引和 get 方法的文档资料更多的关于 Python 2.4 中的 string.Template 类的信息,参看第 1.17 节 1.17 换符串中的子串质Python 2.4 感谢John Nielsen、Lawrence Oluyede、Nick Coghlan 任务 在 Python 2.4 的境,你想完的任务给定一个符串,通过查一个符串 换,将符串中被标记的子符串换掉 34 第 1 解方案 Python 2.4 供了一个新的 string.Template 类,用于个任务面给出一段 码展示怎使用个类 import string # 从字符串生成模板,其中标识符被 $标记 new_style = string.Template('this is $thing') # 给模板的 substitute 方法传入一个字典参数并调用之 print new_style.substitute({'thing':5}) # 输出:this is 5 print new_style.substitute({'thing':'test'}) # 输出:this is test # 另外,也可以给 substitute 方法传递关键字参数 print new_style.substitute(thing=5) # 输出:this is 5 print new_style.substitute(thing='test') # 输出:this is test 论 Python 2.3 中,用于标记质换的符串格式被写更加繁琐的形式 old_style = 'this is %(thing)s' 标识符被在一括弧中,括弧前面一个%,面一个 s然,需要使用%操作符, 使用的格式是将需要处理的符串在%操作符边并在边 print old_style % {'thing':5} # emits: this is 5 print old_style % {'thing':'test'} # emits: this is test 当然,的码在 Python 2.4 中常作过,新的 string.Template 供了 一个更简单的方法 当你创建 string.Template 实例时,在符串格式中,用个美元符$来 表$, 那需要被换的标识面直接跟用于换的文本或者数,并用一花 括号{ }将它们括来面是一个例子 form_letter = '''Dear $customer, I hope you are having a great time. If you do not find Room $room to your satisfaction, let us know. Please accept this $$5 coupon. Sincerely, $manager ${name}Inn''' letter_template = string.Template(form_letter) print letter_template.substitute({'name':'Sleepy', 'customer':'Fred Smith', 'manager':'Barney Mills','room':307, }) 上面的代码片段给出下列输出: Dear Fred Smith, I hope you are having a great time. If you do not find Room 307 to your satisfaction, 文本 35 let us know. Please accept this $5 coupon. Sincerely, Barney Mills SleepyInn 有时,了给 substitute 准备一个做参数,简单的方法是定一本地量,然 将所有量交给 locals()函数将创建一个,的 key 就是本地量, 本地量的值通过 key 来 msg = string.Template('the square of $number is $square') for number in range(10): square = number * number print msg.substitute(locals( )) 一个简单的办法是使用关键参数语法而非,直接将值传递给 substitute msg = string.Template('the square of $number is $square') for i in range(10): print msg.substitute(number=i, square=i*i) 甚时传递和关键参数 msg = string.Template('the square of $number is $square') for number in range(10): print msg.substitute(locals( ), square=number*number) 了防的条目和关键参数显式传递的值发生突,关键参数优比如 msg = string.Template('an $adj $msg') adj = 'interesting' print msg.substitute(locals( ), msg='message') # emits an interesting message 更多资料 Library Reference 文档中关于 string.Template2.4部,及内建的 locals 函数的相 关信息 1.18 一完多个换 感谢Xavier Defrang、Alex Martelli 任务 你想符串的某子串行换 解方案 则表达式虽然易读懂,但有时它的确是快的方法re 象标准中的 re 模 36 第 1 块供的强大 sub 方法,非常利于行高效的则表达式配换面给出一个 函数,该函数返回一个输入符串的拷贝,该拷贝中的所有能够在指定中到的 子串都被换中的值 import re def multiple_replace(text, adict): rx = re.compile('|'.join(map(re.escape, adict))) def one_xlat(match): return adict[match.group(0)] return rx.sub(one_xlat, text) 论 本节展示了怎使用 Python 的标准模块 re 来一完多个子串的换假你有 个基于的符串的映射关系的 key 就是你想要换的子串,而中 key 的值则是被用来做物的符串针的键值关系,调用 符串方法 replace 来完换,它将多处理和创建原文本的复制,但辑很清晰, 度错过 re.sub 的回调函数机制处理方式得更加简单 首,们据想要配的 key 创建一个则表达式个则表达式形式 a1|a2|...|aN,由 N 个需要被换的符串,并被竖线隔开,创建的方法很简单, 如码所示,一行码完然,们直接给 re.sub 传递用于换的符串,而是 传入一个回调函数参数,每当遇到一配,re.sub 就会调用该回调函数,并将 re.MatchObject 的实例作唯一参数传递给该回调函数,并期望着该回调函数返回作 换物的符串在本例中,回调函数在中查配的文本,并返回了值 本节展示的函数 multiple_replace,每被调用时都会新计算则表达式并新定 one_xlat 辅助函数但你经常需要使用一个固定的翻译表来完很多文本的 换,种情况许会希望做一准备作出于种需求,许会使用面的 基于包的方式 import re def make_xlat(*args, **kwds): adict = dict(*args, **kwds) rx = re.compile('|'.join(map(re.escape, adict))) def one_xlat(match): return adict[match.group(0)] def xlat(text): return rx.sub(one_xlat, text) return xlat 给 make_xlat 函数传递一个参数,或者他的传递给内建的 dict 用于创 建一个的参数合make_xlat 返回一个 xlat 包,它需要一个符串参数 text,并返回 text 的一个拷贝,该拷贝是据给出的翻译表完了换之的结果 文本 37 面给出用函数的例子通常们把个段中的示例码作本节给出的 码源文的一部,段码到前面的 Python 语的保会被执行,除非个 模块被作脚本被执行 if _ _name_ _ == "_ _main_ _": text = "Larry Wall is the creator of Perl" adict = { "Larry Wall" : "Guido van Rossum", "creator" : "Benevolent Dictator for Life", "Perl" : "Python", } print multiple_replace(text, adict) translate = make_xlat(adict) print translate(text) 本节中的换任务常常是基于单词的换任务,而是基于任意一个子符串通过 殊的 r’\b’序列,则表达式很好地出单词的开始和结束置们修改 multiple_replace 和 make_xlat 中创建和配则表达式 rx 的部,而完一自定 任务 rx = re.compile(r'\b%s\b' % r'\b|\b'.join(map(re.escape, adict))) 余的码和本节前面给出的一但是,种码相似性是好那意味着 们需要很多相似的本,每个创建则表达式的部都有点,们能会需 要做大量的复制贴作,是码复用中糟糕的情况,外在来的维 增加了麻烦 编写好码的一个关键规则是一,做一!当们注意到码复的时 候,该能够很快嗅到妙的气味,并原来的码行构高复用性 因,了便于定制,们更需要的是一个类,而是函数或者包面给出 个例子,们实了一个类,功能似于 make_xlat,但能够通过子类化和载 行定制 class make_xlat: def _ _init_ _ (self, *args, **kwds): self.adict = dict(*args, **kwds) self.rx = self.make_rx( ) def make_rx(self): return re.compile('|'.join(map(re.escape, self.adict))) def one_xlat(self, match): return self.adict[match.group(0)] def _ _call_ _ (self, text): return self.rx.sub(self.one_xlat, text) 是 make_xlt 函数的一个完全的一方面,们在之前展示的码,由于有 if _ _name_ _ == ‘_ _main_ _’来保,使 make_xlat 由前的函数了类,会有 题函数更加简单快,但是类的优势是通过面向象的方法质子类化或 38 第 1 载某函数,轻易地实新定制了单词行翻译换,码写 class make_xlat_by_whole_words(make_xlat): def make_rx(self): return re.compile(r'\b%s\b' % r'\b|\b'.join(map(re.escape, self.adict))) 通过简单的子类化和载来实定制化,们避免了码的大量的复制和贴, 是有时们宁舍更加简单的函数或者包用,而使用面向象结构的原因 仅仅把相关的功能打包一个类并能自动实需要的定制要实高度的定制性, 在把功能划独立的类方法时必有一定的前瞻性幸好,你用自第一 就把码写得很完美如果码中没有能够符合任务需求的内部结构时在个例子 中,们通过子类化和选择性载来复用码,而且该码行构, 构建出符合需求的结构当然需要行一合的测试来确保没有把原有的辑破坏 掉,时,你完了自的思想内容的构 http://www.refactoring.com 获得更多关于构的艺术和实践的信息 更多资料 Library Reference 和 Python in a Nutshell 中关于 re 模块的文档Refactoring 页 http://www.refactoring.com 1.19 检查符串中的结束标记 感谢Michele Simionato 任务 给定一个符串 s,你想检查 s 中是含有多个结束标记中的一个需要一种快捷优 的方式,来换掉 s.endswith(end1)s.endswith(end2)或 s.endswith(end3)之类的笨 用法 解方案 于类似于本节的题,itertools.imap 给出了一种快方便的解办法 import itertools def anyTrue(predicate, sequence): return True in itertools.imap(predicate, sequence) def endsWith(s, *endings): return anyTrue(s.endswith, endings) 论 一个型的 endsWith 用是打印出当前目录中所有的文 文本 39 import os for filename in os.listdir('.'): if endsWith(filename, '.jpg', '.jpeg', '.gif'): print filename 本节解方案中给出的思想很容易地用到他类似的检查任务中去辅助函数 anyTrue 是一个通用而快的函数,给它传入他的被绑定方法(bound method)作 第一个参数,比如 s.startswith 或 s._ _contains_ _实,使用辅助函数而直接编 码许更好 if anyTrue(filename.endswith, (".jpg", ".gif", ".png")): 认它的读性没题 被绑定方法Bound Method 如果一个 Python 对象提供一个方法,可以直接获得一个已经绑定到该对象的方法, 从而直接使用此方法。比如,可以将其赋值给别的对象、将它作为一个参数传递、 或者在一个函数中直接返回它,等等。举个例子 L = ['fee', 'fie', 'foo'] x = L.append 现在 x 指向了列表对象 L 的一个被绑定方法。调用 x,比如 x(‘fum’),和调用 L.append(‘fum’)是完全等价的结果都是对象 L 变成了 ['fee', 'fie', 'foo', 'fum']。 如果访问的是一个类型或者一个类的方法,而不是一个类型或者类的实例的方法,你得 到的是一个非绑定方法,该方法并未“依附”于此类型或者类的任何一个实例当调用 它时,需要提供该类型或类的一个实例作为第一个参数。比如,如果设定 y = list.append, 你不能直接调用 y(‘I’),因为 Python 猜不出你想给哪个列表对象添加一个 I。可以调用 y(L,’I’),这和调用 L.append(‘I’)效果完全一样只要 isinstance(L,list)成立。 本节的解方案和想法来源于 news:comp.lang.python 的一个论,并综合和概括了很 多人的点,包括了 Raymond HettingerChris PerkinsBengt Richter 等 更多资料 Library Reference 和 Python in a Nutshell 中关于 itertools 和符串方法的内容 1.20 使用 Unicode 来处理化文本 感谢Holger Krekel 任务 需要处理包含了非 ASCII 符的文本符串 40 第 1 解方案 在一使用普通的节串 str 类型的场合,使用 Python 供的内置的 unicode 类 型用法很简单,要接了在节串和 unicode 符串之间的显式转换的方式 >>> german_ae = unicode('\xc3\xa4', 'utf8') german_ae 是一个 unicode 符串,表了小写的德语元音音umlaut,或他 音符符æ据指定的 UTF-8 编码方式,通过解析单节符串'\xc3\xa4', 段码创建了一个 unicode 符串有很多他的编码方式,过 UTF-8 常用, 因它是通用的UTF-8 编码任何 unicode 符串,而且和 7 的 ASCII 符兼容任何 ASCII 单节符串,是确的 UTF-8 编码符串 一旦跨过一屏障,生活就得更美好了!像处理普通的 str 符串那操纵 unicode 符串 >>> sentence = "This is a " + german_ae >>> sentence2 = "Easy!" >>> para = ". ".join([sentence, sentence2]) 注意,para 是一个 unicode 符串,是因一个 unicode 符串和一个节串之间的 操作总会产生一个 unicode 符串质除非个操作发生错误并抛出常 >>> bytestring = '\xc3\xa4' #某个非 ASCII 字节串 >>> german_ae += bytestring UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128) 符’0xc3’是 7 ASCII 编码中的有效符,Python 拒猜测编码所,在 Python 中使用 unicode 的关键点是,你要时明确编码是 论 如果你遵一规范,并且学会处理一常的题,则 Python 中的 unicode 处理是 非常简单的情是说完一个高效的 Unicode 实是个简单的任务过,如 他的一难题一,无心多管使用 Python 的高效的 Unicode 实就行了 要的一点是,首要完全接节串和 unicode 符串的差如解方案小节 所示,你经常需要通过一个节串和一个编码方式显式地创建一个 unicode 符串 指定编码方式,节串基本没有意,除非你很有气而且碰那个节串是 ASCII 文本 在Python 中使用unicode 符串的常的题是,你在处理的文本一部是 unicode 象,一部则是节串Python 会简单地尝试把你的节串式地转换 unicode 它通常假那是 ASCII 编码,如果中碰含有了非 ASCII 符,它会给你一个 UnicodeDecodeError 的常UnicodeDecodeError 常通知你,你把 Unicode 和节串 文本 41 混在了一,而且 Python 无法它本会去尝试猜测你的节串表何种文本 各个 Python 大目的开发人员们总结出了一简单的规则,来避免种行时的 UnicodeDecodeError 常,该规则被总结一话总是在 IO 动作的关口做转换 面更深入地解释一 • 无论何时,当你的程序接收到了来自外部的文本数据来自网文或 者用户输入等时,当立刻创建一个 unicode 象,出合的编码,如查看 HTTP 头,或者一个合的转化方法来确定所用的编码方式 • 无论何时,当你的程序需要向外部发文本数据发到网写入文 或者输出给用户等时,当探察确的编码,并用那种编码将你的文本转化 节串则,Python 会尝试把 Unicode 转 ASCII 节串,很有能 发生 UnicodeEncodeError 常,好是前面例子中给出 UnicodeDecodeError 的 相情况 遵循个规则,解大多数的 Unicode 题如果你然遇到了那种 UnicodeError 之一,当快检查是忘记了在地方创建一个 unicode 象,或者 忘记了把它转化编码过的节串,或者使用了完全确的编码方式编码错误 有能来自于用户,或者他你的程序行交互的程序,因它们没有遵循编码 规则或惯例 了将一个 Unicode 符串转回到编码过的节串,你通常做 >>> bytestring = german_ae.decode('latin1') >>> bytestring '\xe4' 在,bytestring 是德语中的用’latin1’行编码的 æ 符注意,’\xe4’Latin1及 前面展示的’\xc3\xa4’UTF-8表了的德语符,但使用了的编码 ,该能够了解 Python 拒在几种能的编码中行猜测了 是一种很要的计选择,基于了 Zen of Python 原则中的一条在模糊含混面前拒 猜测在任何一个 Python 的交互式 shell 示符,输入 import this 语,你就 阅读 Zen of Python 中的要原则 更多资料 Unicode 是一个很宽泛的题,值得荐的书有Unicode: A Primer,Tony Graham 著 (Hungry Minds, Inc.),更多节 http://www.menteith.com/unicode/primer/及 Joel Spolsky 写的一篇短小但彻的文,The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses)!体 http://www.joelonsoftware.com/articles/Unicode.html外参阅 Library Reference 和 Python in a Nutshell 中关于内建 str 和 unicode 类型unidata 模块和 codecs 模块 42 第 1 1.21 在 Unicode 和普通符串之间转换 感谢David Ascher、Paul Prescod 任务 需要处理一能符合 ASCII 符的文本数据 解方案 普通符串用多种方式编码 Unicode 符串,体要看你选择了哪种编码 unicodestring = u"Hello world" # 将 Unicode 转化为普通 Python 字符串:"encode" utf8string = unicodestring.encode("utf-8") asciistring = unicodestring.encode("ascii") isostring = unicodestring.encode("ISO-8859-1") utf16string = unicodestring.encode("utf-16") # 将普通 Python 字符串转化为 Unicode:"decode" plainstring1 = unicode(utf8string, "utf-8") plainstring2 = unicode(asciistring, "ascii") plainstring3 = unicode(isostring, "ISO-8859-1") plainstring4 = unicode(utf16string, "utf-16") assert plainstring1 == plainstring2 == plainstring3 == plainstring4 论 如果想处理含有非 ASCII 符的文本数据,首要懂一 Unicode质是 unicode unicode 怎作Python 如何处理 unicode前一节供了少量很要的指,本节 将在个话题继续深入论 用在完全了解 Unicode 的一之,才去处理实世界中的有关 unicode 的题,但 是一基本知识是或缺的首,需要理解节和符之间的别在过去的 ASCII 符体的语言及境中,节和符被认是一种东西一个节 有 256 个的值,因境被限制能处理超过 256 个的符而 一方面,Unicode 则支持千万的符,那意味着每个 unicode 符占用超过 1 个 节的宽度因,首需要搞清楚符和节之间的别 标准的 Python 符串实是节串,种符串中的每个符,长度 1,实 就是一个节们用 Python 的标准符串类型的他一术语来称 8 符串或者普通符串在本节中,们称种类型的符串节串,来醒 你它们的单节的点 Python 的 Unicode 符是一个抽象的象,它足够大,能够容纳任何符, Python 文本 43 的长整数行类比完全无心它的内部表示有当你试将它们传递给一基 于节处理的函数时质比如文的 write 方法和网 socket 的 send 方法,unicode 符的表示才会一个题基于个原因,必选择何种方式将符表示 节将 unicode 符串转化节串被称该符串编码的,如果一个文 socket 或者他基于节的象中载入一个 unicode 符串,必解码,将 节转符 unicode 象转化节串有很多方法,方法都被称某种编码由于一系列历 的治的及术的原因,没有哪种编码是确的每种编码都有个大 小写敏感的,个被传递给 encode 和 decode 函数作参数面给出 一该知道的编码 • UTF-8 编码用于任何 Unicode 符它向兼容 ASCII,所一个纯粹 的 ASCII 文被认是一个 UTF-8 文,而使用 ASCII 符的 UTF-8 文,完全等于使用符的 ASCII 文个属性使得 UTF-8 有极好 的向兼容能力,别是一老的 UNIX 来说UTF-8 是 UNIX 性的编码,时是 XML 文档的默认编码UTF-8 的要弱点是, 于一东方的语言文本,它的效率比较 • UTF-16 是微软操作系统和 Java 境喜爱的编码它于西方语言效率略,但 于东方语言更有效率UTF-16 有一个种,被称 UCS-2 • ISO-8859 系列的编码是 ASCII 的超,每种编码都能处理 256 个的符 编码能支持所有的 Unicode 符它们支持一定的语系或语言 ISO-8859-1,Latin-1的人所知,覆盖了大多西欧和非洲的语言,但 包括阿拉语ISO-8859-2,被称Latin-2,覆盖了很多东欧的语言,比 如匈牙利和波ISO-8859-15,在在欧洲非常流行,基本它和 ISO-8859-1 一 ,但增加了欧洲币符号的支持 如果想所有的 Unicode 符编码,你能需要用 UTF-8当需要处理别的程序或者输 入备用他编码创建的数据时,你才能需要用到他编码,或者相,在需要输 出数据给你的游程序或者输出备,而它们采用的是一种定的编码时第 1.22 节展示了一个例子,个例子中,看到怎通过程序的标准输出来驱动他游 程序或备 更多资料 Unicode 是一个宽泛的题,值得荐的书有Unicode: A Primer,Tony Graham 著 Hungry Minds, Inc.,更多节 http://www.menteith.com/unicode/primer/及 Joel Spolsky 写的一篇短小但彻的文,The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses)!体 44 第 1 http://www.joelonsoftware.com/articles/Unicode.html外参阅 Library Reference 和 Python in a Nutshell 中关于内建 str 和 unicode 类型unidata 模块和 codecs 模块及 第 1.20 节和第 1.22 节 1.22 在标准输出中打印 Unicode 符 感谢David Ascher 任务 你想将 Unicode 符串打印到标准输出中比如了调试,但是符串并符合 默认的编码 解方案 通过 Python 标准中的 codecs 模块,将 sys.stdout 流用转换器包装来比如,如果 你知道输出会被打印到一个端,而且该端 ISO-8859-1 的编码显式符, 编写码 import codecs, sys sys.stdout = codecs.lookup('iso8859-1')[-1](sys.stdout) 论 Unicode 涵盖极广,全世界的语言符都在 Unicode 的表示范围之内,外,Unicode 符串的内部表示 Unicode 使用者没有关系一个用于处理节的文流,比如 sys.stdout,都有自的编码通过修改 site 模块改默认的编码,该文流将 新文使用新编码过,需要完全改你的 Python 装,而且他一程序 则能会被搞乱,它们依然会按照你原的编码置作一般是型的 Python 标准 编码,ASCII因,种修改并值得荐 本节的方法则用了一个将 sys.stdout 绑定到一个使用 Unicode 输入和 ISO-8859-1 就是 Latin-1输出的流种方法并改之前 sys.stdout 的任何编码,如面 码所示首,们用一个量指向原来的基于 ASCII 的 sys.stdout >>> old = sys.stdout 然,们创建一个 Unicode 符串,个符串通常情况是能通过 sys.stdout 输出的 >>> char = u"\N{LATIN SMALL LETTER A WITH DIAERESIS}" >>> print char Traceback (most recent call last): File "", line 1, in ? UnicodeError: ASCII encoding error: ordinal not in range(128) 文本 45 如果个操作没有出错误,那是因 Python 认它知道你的端用了编码 别是,如果你的端是 IDLE质Python 所的免费的开发境,Python 极有 能能够确认确的编码如果出了错误,或者没有示错误,但是输出的符 是你期望的,那是因你的端使用了 UTF-8 编码,而 Python 知道如果 属于者的情况,用 codecs 流 sys.stdout 行包装解 UTF-8 编码题,将 sys.stdout 绑定到被封装过的流,然新试一 >>> sys.stdout = codecs.lookup('utf-8')[-1](sys.stdout) >>> print char ä 个方法在你的端端模拟器或者他类型的交互式 Python 解释窗口支持 UTF-8 编码时才有效,而且有极强的符表力,能够显示出任何需要的符如 果没有的程序或备,在因网一个用于你的的免费的程序 Python 会尝试确认你的端的编码,并把编码的在 sys.stdout.encoding 中作 一个属性有时但是总是,它能够判断出确的编码IDLE 经 sys.stdout 行了包装,如本节解方案的方法一,所,在 Python 的交互式境之, 直接打印出 Unicode 符串 更多资料 Library Reference 和 Python in a Nutshell 中关于 codecs 和 site 模块及 sys 模块中的 setdefaultencoding第 1.20 节和第 1.21 节 1.23 Unicode 数据编码并用于 XML 和 HTML 感谢David Goodger、Peter Cogolo 任务 你想 Unicode 文本行编码,使用一种有限制,但很流行的编码,如 ASCII 或 Latin-1,并将处理的结果用于 HTML 输出或者某 XML 用 解方案 Python 供了一种编码错误处理,做 xmlcharrefreplace,它会将所有属于所选 编码的符用 XML 的数符引用来 def encode_for_xml(unicode_data, encoding='ascii'): return unicode_data.encode(encoding, 'xmlcharrefreplace') 将法用于 HTML 输出,过你能会更喜 HTML 的符号实体引用出于 个目的,需要定并注一个自定的编码错误处理函数要实一个处理函 46 第 1 数非常简单,因 Python 标准经供了一个做 htmlentitydefs 的模块,包含了所 有的 HTML 实体定 import codecs from htmlentitydefs import codepoint2name def html_replace(exc): if isinstance(exc, (UnicodeEncodeError, UnicodeTranslateError)): s = [ u'&%s;' % codepoint2name[ord(c)] for c in exc.object[exc.start:exc.end] ] return ''.join(s), exc.end else: raise TypeError("can't handle %s" % exc._ _name_ _) codecs.register_error('html_replace', html_replace) 注完错误处理函数之,写个包装函数,简化使用 def encode_for_html(unicode_data, encoding='ascii'): return unicode_data.encode(encoding, 'html_replace') 论 如他的一 Python 模块一,个模块将供一个测试的示例,由 if _ _name_ _ == '_ _main_ _'一行语行保 if _ _name_ _ == '_ _main_ _': # demo data = u'''\ Encoding Test

accented characters:

  • \xe0 (a + grave)
  • \xe7 (c + cedilla)
  • \xe9 (e + acute)

symbols:

  • \xa3 (British pound)
  • \u20ac (Euro)
  • \u221e (infinity)
''' print encode_for_xml(data) print encode_for_html(data) 文本 47 如果将模块作脚本来行,你会看到如的输出来自于 encode_for_xml
  • à (a + grave)
  • ç (c + cedilla)
  • é (e + acute) ...
  • £ (British pound)
  • € (Euro)
  • ∞ (infinity) 有来自于 encode_for_html
  • à (a + grave)
  • ç (c + cedilla)
  • é (e + acute) ...
  • £ (British pound)
  • € (Euro)
  • ∞ (infinity) 段输出都很清晰,过 encode_for_xml 更加有通用性它用于任何 XML 用,仅仅是 HTML,但 encode_for_html 能够生更易读的结果质如果希望直 接读或者编辑那结果如果给浏览器供种形式的数据,能够看到渲染输出 实是一的了看到种方式在浏览器中的展示,将述码作脚 本行,将输出向到一个磁盘文,并用文本编辑器将种输出隔开来,然 用浏览器别查看或者,行脚本,一将调用 encode_for_xml 的输出注释 掉,一将 encode_for_html 的输出注释掉 记,Unicode 数据在被打印或者写到文之前一定要编码由于 UTF-8 能够处 理任何 Unicode 符,所它是理想的编码但很多用户和用而言,ASCII 或 Latin-1 比 UTF-8 更迎当 Unicode 数据包含了指定编码之外的符时比如,一 音符和很多符号大多在 ASCII 或 Latin-1 之中,比如,Latin-1 无法表无 符号,单靠编码本身,本无法处理数据Python 供一个内建的编 码错误处理函数,做 xmlcharrefreplace,将所有的能被编码的符换 XML 的 数符引用,比如∞,表示无符号本节展示了怎编写和注 一个类似的错误处理函数 html_replace,针 HTML 输出行处理html_replace 将能编码的符换更读性的 HTML 符号实体引用,比如用∞来表 示无符号html_replace 相比于 xmlcharrefreplace,它的通用性没那好,因 它并支持所有的 Unicode 符,时能用于非 HTML 的用但如果你希望 HTML 输出的源码文能够有更好的读性,html_replace 是非常有用的 如果输出的是 HTML 或者任何形式的 XML,错误处理函数就没意了 比如,TeX 和他的标记语言并认识 XML 的数符引用,但如果你知道怎创 建一个该标记语言中的符引用,修改本节示例中的错误处理函数 html_replace, 48 第 1 并注一个新的符合需求的处理函数 当需要将 Unicode 数据写入文时,使用 Python 标准的 codecs 模块供的一 个非常高效指定编码和错误处理函数的方法 outfile = codecs.open('out.html', mode='w', encoding='ascii', errors='html_replace') 在,将 outfile.write(unicode_data)用于任何 Unicode 符串 unicode_data,所有 的编码和错误处理都会明地自动行当然,在输出完之,得调用 outfile.close() 更多文档 Library Reference 和 Python in a Nutshell 中关于 codecs 模块和 htmlentitydefs 1.24 某符串大小写敏感 感谢Dale Strickland-Clark、Peter Cogolo、Mark McMahon 任务 你想某符串在比较和查的时候是大小写敏感的,但在他操作中保持 原状 解方案 好的解方式是,将种符串封装在 str 的一个合的子类中 class iStr(str): """ 大小写不敏感的字符串类 行为方式类似于 str,只是所有的比较和查询 都是大小写不敏感的 """ def _ _init_ _ (self, *args): self._lowered = str.lower(self) def _ _repr_ _ (self): return '%s(%s)' % (type(self).伯_ _name_ _, str.伯_ _repr_ _(self)) def _ _hash_ _(self): return hash(self._lowered) def lower(self): return self._lowered def _make_case_insensitive(name): ''' 将 str 的方法封装成 iStr 的方法,大小写不敏感 ''' str_meth = getattr(str, name) def x(self, other, *args): 文本 49 ''' 先尝试将 other 小写化,通常这应该是一个字符串, 但必须要做好准备应对这个过程中出现的错误, 因为字符串是可以和非字符串正确地比较的 ''' try: other = other.lower( ) except (TypeError, AttributeError, ValueError): pass return str_meth(self._lowered, other, *args) # 仅 Python 2.4,增加一条语句: x.func_name = name setattr(iStr, name, x) # 将_make_case_insensitive 函数应用于指定的方法 for name in 'eq lt le gt gt ne cmp contains'.split( ): _make_case_insensitive('_ _%s_ _' % name) for name in 'count endswith find index rfind rindex startswith'.split( ): _make_case_insensitive(name) # 注意,我们并不修改 replace、split、strip 等方法 # 当然,如果有需要,也可以对它们进行修改 del _ ake_case_insensitive # 删除帮助函数,已经不再需要了 m 论 iStr 类的一实的选择很值得论首,们在_ _init_ _中一性生了小写 本,是因们认识到在 iStr 的型用中,个小写本将会被复地使用 们在一个私有的量中保个小写本,将作一个属性,当然,别保得 过了它一个划线开头,而是个划线,因如果 iStr 派生子类比 如,一扩展,支持大小写敏感的和换等,如解方案注释中 所说的,iStr 的子类很有能会需要父类 iStr 的一关键的实节 们没有供他一方法的大小写敏感的本,如 replace,因个例子 经清晰地展示了一种通用的建立输入和输出之间联系的方式据用行别定制 的子类将供能够满足需求的功能比如,replace 方法并没有被封装,则们一 个 iStr 的实例调用 replace,返回的是 str 的实例,而是 iStr如果会给你的用带 来题,将所有的返回符串的 iStr 方法封装来,就确保所有返回的结 果是 iStr 的实例基于个目的,需要一个单独的助手函数,相似但完全等于 解方案中给出的_make_case_insensitive def _make_return_iStr(name): str_meth = getattr(str, name) def x(*args): return iStr(str_meth(*args)) setattr(iStr, name, x) 需要所有返回符串的方法的用个助手函数,_make_return_iStr for name in 'center ljust rjust strip lstrip rstrip'.split( ): 伯伯伯伯伯伯伯_make_return_iStr(name) 符串有 20 种方法包括一殊方法,比如_ _add_ _和_ _mul_ _,需要考虑哪 50 第 1 方法该被封装来把一额外的方法,比如 split 和 join它们能需要 一别的处理封装来,或者他的方法,如 encode 和 decode,于类方法, 除非定了一个大小写敏感的 unicode 子类型,则无法处理它们而实,针 一个定的用,能是所有的封装的方法都会引题如你所的那, 由于 Python 符串的方法和功能很丰富,要想用一种通用的依赖于定用的 方式,完全彻地定制出一个子类型,是要花点功的 iStr 的实很谨慎,要是了避免一复性的例行公般的码通常是冗长且容 易滋生 bug 的码,如果们用普通的方式载 str 每一个需要的方法,在类的实 中写一堆 def 语,很有能就会陷入种尴尬的境地使用自定的元类或者 他的高术个例子而言会有别的优势,但使用一个辅助函数来生 和装封装层包,就轻易地避开题然们在个循中使用该辅助函数, 一个循处理常用的方法,一个则处理殊的方法个循都必被置在 class 语之,如们在解方案中给出的码所示,是因个循需要修 改 iStr 类象,但除非用 class 语完 iStr 类的声明,则那个类象本就 在因当然无法修改 在 Python 2.4 中,新指定函数象的 func_name 属性,在本例中,当 iStr 实例 用内省机制时,用种方法码得更加清晰和易读但在 Python 2.3 中,函 数象的 func_name 属性是读的因,在本节的论中,们仅仅是指出了一 种能性,们想因个小题失去 Python 2.3 的兼容性 大小写敏感但保留了大小写信息的符串有很多用途,包括高用户输入 行解析的宽松度,在文系统比如 Windows 和 Macintosh 的文系统中查 包含指定符的文,等等你能会发,有很多地方需要大小写敏感的 容器类型,比如列表合等质它们都需要在某场合,忽略掉 key 或者子 的大小写的信息很明显,一个好的方法是一性构建出大小写敏感的比较 和查功能在你的箱中经增加了本节供的解方案,符串行 任何需要的封装和定制,你甚能定制他一你希望备大小写敏感能力 的容器类型 比如,一个所有子都是符串的列表,你希望能够行一大小写无关的处理如 用 count 和 index 行排序,完全基于 iStr 的实,轻易地构建一个 iList class iList(list): def _ _init_ _(self, *args): list._ _init_ _(self, *args) # 依赖_ _setitem_ _将各项封装为 iStr self[:] = self wrap_each_item = iStr def _ _setitem_ _(self, i, v): if isinstance(i, slice): v = map(self.wrap_each_item, v) 文本 51 else: v = self.wrap_each_item(v) list._ _setitem_ _(self, i, v) def append(self, item): list.append(self, self.wrap_each_item(item)) def extend(self, seq): list.extend(self, map(self.wrap_each_item, seq)) 本,们做的情是把 iList 实例中每个子都通过调用 iStr 来封装,余部则 保持原状 外一,iList 的实方式使得据用供定的 iStr,轻易地完子 类的定制需在 iList 的子类中载员量 wrap_each_item 更多资料 Library Reference 和 Python in a Nutshell 中关于 str 的节,要是符串方法,及 一殊的用于比较和哈希的方法 1.25 将HTML文档转化文本显示到UNIX 端 感谢Brent Burley、Mark Moraes 任务 需要将 HTML 文档中的文本展示在 UNIX 端,时要支持体和划线的 显示 解方案 简单的方法是写一个过滤的脚本,标准输入接收 HTML,将输出文本和端制 序列打印到标准的输出由于本节的题针 UNIX,们借助 Python 标准 的 os 模块供的 popen 函数,通过 UNIX 的命 tput 获所需的端制序列 #!/usr/bin/env python import sys, os, htmllib, formatter # 使用 UNIX 的 tput 来获得粗体、下划线和重设的转义序列 set_bold = os.popen('tput bold').read( ) set_underline = os.popen('tput smul').read( ) perform_reset = os.popen('tput sgr0').read( ) class TtyFormatter(formatter.AbstractFormatter): ''' 一个保留粗体和斜体状态的格式化对象,并输出 相应的终端控制序列 ''' def _ _init_ _(self, writer): # 首先,像往常一样,初始化超类 formatter.AbstractFormatter._ _init_ _(self, writer) 52 第 1 # 一开始既没有粗体也没有斜体状态,未保存任何信息 self.fontState = False, False self.fontStack = [ ] def push_font(self, font): # font 元组有 4 项,我们只看与粗体和斜体的状态 # 有关的两个标志 size, is_italic, is_bold, is_tt = font self.fontStack.append((is_italic, is_bold)) self._updateFontState( ) def pop_font(self, *args): # 回到前一个 font 状态 try: self.fontStack.pop( ) except IndexError: pass self._updateFontState( ) def updateFontState(self): # 输出正确的终端控制序列,如果粗体和 /或斜体 ==underline # 的状态被刚刚改变的话 try: newState = self.fontStack[-1] except IndexError: newState = False, False if self.fontState != newState: # 相关的状态改变:重置终端 print perform_reset, # 如果需要的话,设置下划线与 /或粗体状态 if newState[0]: print set_underline, if newState[1]: print set_bold, # 记住当前的两个状态 self.fontState = newState # 生成写入、格式化、解析对象,根据需要将它们连接起来 myWriter = formatter.DumbWriter( ) if sys.stdout.isatty( ): myFormatter = TtyFormatter(myWriter) else: myFormatter = formatter.AbstractFormatter(myWriter) myParser = htmllib.HTMLParser(myFormatter) # 将标准输入和终端操作提供给解析器 myParser.feed(sys.stdin.read( )) myParser.close( ) 论 Python 标准供的 formatter.AbstractFormatter 类,在任何场合作一方面, 它的子类 TtyFormatter 供的一改良,则要是了操纵和使用类 UNIXUNIX-like 文本 53 端,体地说,就是通过 UNIX 命 tput 获制体和划线的转序列,并 将端置基本状态 很多系统并没有通过 UNIX 认证,比如 Linux 和 Mac OS X,但它们供了一个用 的 tput 命,因本节的 TtyFormatter 子类在的系统中然常作或者 说,用更宽泛的眼来看待本节及的UNIX,就好像们在一他 论中所用的UNIX概念如果你愿意,认它指的是*ix 如果你的端模拟器支持他的一制输出表的转序列,据情况 修改 TtyFormatter 类比如,据说在 Windows 中,cmd.exe 命能够支持所有标准的 ANSI 转序列,所如果你想在 Windows 行你的脚本,用硬编码的方式在 类中写入那序列 很多时候,你能会更喜用 UNIX 经供的命,比如 lynx -dump -,相比于本节 方案中供的方法,命能够供更丰富更表力的输出但有时你会发 Python 装在一个供种有用的命如 lynx的系统,那本节给出的方法 就显得很方便和简洁了 更多资料 Library Reference 和 Python in a Nutshell 文档中关于 formatter 和 htmllib 模块的内容在 UNIX 或者类 UNIX 系统中用 man 命查看 tput 命的有关信息 54 第 1 第 2 文 引言 感谢:Mark Lutz,Programming Python 和 Python Quick Reference 的作者,Learning Python 的合著者之一 任何一个有经验的程序员接触一门新语言时,都会首先在语言的箱中文 的相关因处理外部文是一种非常实用和常的任务,语言的文处理接 口计的好坏,在很大程度决定着门语言的实用性 如本节将要绍那,Python 在方面非常秀Python 中文的支持,体 在很多层内建的 open 函数标准文象类型的一个词,到标准 模块供的一些特定的,如 os,再到遍布网的种第方供的实用 一言蔽之,Python 大的文供了种方法来文 文基础 在 Python 中,文象是内建类型 file 的实例内建函数 open 会建并返回一个文 象第一个参数是一个符串,指定了文路文之前有一个选的目录路 open 的第个参数是个符串,指定了打开文的模式如 input = open('data', 'r') output = open('/tmp/spam', 'w') open 接由斜线符/隔开的目录和文构的文路,而完全管作系 统本身的倾向在使用斜线的系统中,使用反斜线符\,过如果没有什 好理由的话好要做反斜线在符串中的文本表示美,你得用个反 斜线来表示单个斜线符或者用原符串raw string如果文路参数中没有 包括文的路,文被认处于前作路中个路能游离于 Python 模块的搜索路之外 国国 55 于模式参数,使用’r’意味着文本模式读文是默认的值而常常被忽略, open 使用第一个参数他常的模式是’rb’,表示进制模式读 文,’w’表示建文并文本模式写文,’wb’表示建并进制模式写文’r’ 的一种体是’rU’,意味着将支持通用换行符universal newline的文本模式读 文’rU’模式用一种独立于文用的断行定的方式来读文,是 UNIX 方式,是 Windows 方式,甚是老的Mac 方式的 Mac OS X 方面来讲都算是 UNIX,但前几的 Mac OS 9 完全一 于那些非类 UNIX non-UNIX-like platforms,文本模式和进制模式之间的 别非常要,和那些系统用的行结束标记有关你进制模式打开一个文, Python 知道它无关心行结束标记它是在文和内中的符串之间移动节, 需要任何翻译转化但你在一个非类 UNIX 系统中文本模式打开一个文时, Python 知道它必在符串用的\n换行符和前系统用的行结束标记之间做翻 译和转化作要你能确地指定你打开的文的模式,你的有的 Python 码总 依赖\n作行结束标记 一有了一个文象,你就执行象的种 I/O 作,们接来会绍那些 作你完了处理,象调用 close 方法来完收作,关有和 个文象的联系 input.close( ) 在一些短的脚本中,用户常常忽略个骤,因一个文象在垃圾回收阶段被 收回的时候在流的 Python 中意味着文会被立关,但他的一些要的 Python 实,如 Jython 和 IronPython,有他更宽的垃圾回收策略,Python 会 自动关文象管怎,用完文象之能马关文是一个好的 编程惯,尤是在写一些大型程序的时候,然你总会一些无用的打开的文 象着内资源注意,Try/finally 在确保文被关时非常好用,使函数抛了一 个无法处理的常,并因而终 在写入文的时候,使用 write 方法 output.write(s) 假如 s 是一个符串, output 是用文本模式打开的时候,将 s 看是符串, output 进制模式打开的时候,将 s 看做是节串文有他的一些写 入有关的方法,如 flush,发缓中有的数据,有 writelines,一调用 将整个符串序列写入过,write 然是常用的方法 文中读数据把数据写入文更常,涉及更多的内容,因文象的有 关读的方法要写入的方法多readline 方法一个文本文中读并返回一行文 本数据考虑一个循 56 第 2 while True: line = input.readline( ) if not line: break process(line) 在 Python 中,经是一个文读并处理每行数据的惯用的方式,但在经 是了一个过时的方式是使用 readlines 方法,一读完整个文,并返回一个 行数据的列表 for line in input.readlines(): process(line) readlines 方法有在物理内足够用的情况才会很有用如果文非常庞大,readlines 能会失败,或者性能急剧降虚拟内足,作系统会将物理内中的数据复 制到磁盘在在的 Python 中,需个文象执行一个循,每得一行 并处理,获得更好的性能和效率 for line in input: process(line) 然,总是逐行的读一个文读文中的一些或者有的节,特别 是在用进制模式读的情况于进制数据,行是一个没有意的概念据 种情况,使用 read 方法指定参数时,read 会读并返回文中有剩余 的节 read 被传入一个整数参数 N 并调用时,它读并返回 N 个节或者 有剩余的节,如果剩余的节数少于 N他的值得一的是 seek 和 tell 方法, 支持文的机在处理包含很多固定长度记录的进制文时,些方法很 常用 移植性和灵活性 表面看,Python 文的支持非常直接然而,在你仔细阅读本的码之前, 想指关于 Python 文支持的个要方面码的移植性和接口的灵活性 请记,Python 中的大多数文接口都是完全跨的种计的要性怎赞美 过如,在一个在目录树中搜索有文中的文本的脚本,一点码改 地行在个需要将脚本文复制到目标机器一直都是做的— 完全用关心作系统的差性由于 Python 的大的移植性,行几 一个无足轻的题 有,Python 的文处理接口往往并局限在用于真实的物理文,很 惊实,大多数文用于那些暴露的接口文象类似的任何类型 的象因,文读者需要关心读方法,文写入者需关心写入方法 要目标象实了期望的协议,一作都滑自然地进行 如,假你要写一个通用的文处理函数,像面,传入一个函数来处理输入 文 57 文中的每行数据 def scanner(fileobject, linehandler): for line in fileobject: linehandler(line) 如果将个函数写入一个模块文,并将文放入包括在 Python 搜索路sys.path 中的一个目录,就在任何时候调用函数来逐行扫文本文,无论何时 了展示一点,面给一个客户脚本,是简单地打印每行的第一个单词 from myutils import scanner def firstword(line): print line.split( )[0] file = open('data') scanner(file, firstword) ,一都很好们完了一个小的复用的软组但请注意,在 scanner 函数中并没有类型有任何假,要是能被逐行迭的满足接口要求的象就 如,假如你想测试一些来自于符串象的输入,而是真实的物理文标准 的 StringIO 模块,及等但更快的 cStringIO,供了一种用的封装和类似的接口 from cStringIO import StringIO from myutils import scanner def firstword(line): print line.split( )[0] string = StringIO('one\ntwo xxx\nthree\n') scanner(string, firstword) StringIO 象完全兼容文象,插用,,scanner 内中的一个符串 象中读行文本,而是一个真的外部文中读完全用更改原先的 scanner 来完任务,需传递一个确的象了更通用性,实一个类并供 scanner 期望的接口 class MyStream(object): def 普 普iter普 普(self): # 获取并返回字符串 return iter(['a\n', 'b c d\n']) from myutils import scanner def firstword(line): print line.split( )[0] object = MyStream( ) scanner(object, firstword) , scanner 尝试读文时,它实调用的是在类中实的普 普iter普 普方法在实 践中,个方法借助 Python 标准种源抓文本一个交互的用户,一个 式的图形界面输入框,一个 shelve 象,一个 SQL 数据,一页 XML 或者 HTML, 一个网 socket 等关键点在于,scanner 本就知道关心究是什类型的 象实了它期望的接口,及些接口究在背地什 面向象语言的程序员都知道一个要的概念,多被处理象的类型决定了究 58 第 2 进行什的作—如 scanner 中的循迭而在 Python 中,象的接口,而非 类型,才是真的接器种机制的实效果是一个函数的用面往往要你 想的广得多特别是如果你有一些静类型语言如 C 或 C++的背,会有更深 的体会就好像们在 Python 中突然获得了免费的 C++模Python 的大的动类 型带来的一个副产品是,码有了生俱来的灵活性 然,码的移植性和灵活性在 Python 的开发中无处在,仅仅局限于文接 口者都是语言本身的特性,段文处理脚本是简单地继了些特性 他的 Python 的点,包括码容易编写和易读,需要修改你的文处理程序的时 候,你就能体会到些好处了Python 除了些值得赞美的点之外,在本和本书 中有很多精彩的内容和细节值得你去发掘和探索阅读愉快! 2.1 读文 感谢:Luther Blissett 任务 你想文中读文本或数据 解决方案 方便的方法是一性读文中的有内容并放置到一个大符串中 all普the普text = open('thefile.txt').read( ) # 文本文件中的所有文本 all普the普data = open('abinfile','rb').read( ) # 二进制文件中的所有数据 了全起,好是给打开的文象指定一个,在完作之 迅关文,防一些无用的文象用内举个例子,文本文读 file普object = open('thefile.txt') try: all普the普text = file普object.read( ) finally: file普object.close( ) 一定要在用 Try/finally 语,但是用了效果更好,因它保证文象被 关,使在读中发生了错误 简单快, Python 风格的方法是逐行读文本文内容,并将读的数据 放置到一个符串列表中 list普 f普all普the普lines = file普object.readlines( ) o 读的每行文本都带有\n符号如果你想,有一个的办 法,如 文 59 list普of普all普the普lines = file普object.read( ).splitlines( ) list普of普all普the普lines = file普object.read( ).split('\n') list普 f普all普the普lines = [L.rstrip('\n') for L in file普object] o 简单快的逐行处理文本文的方法是,用一个简单的 for 循语 for line in file普object: rocess line p 种方法会在每行留\n符号在 for 循的体部加一 line line.rstrip('\n') = 或者,你想去除每行的的空符是’\n’\,常的办法是 line line.rstrip( ) = 论 除非要读的文非常大,然一性读有内容放进内并进一处理是 快和方便的办法内建函数 open 建了一个 Python 的文象外, 通过调用内建类型 file 建文象你象调用 read 方法将读有内容 无论是文本是进制数据,并放入一个大符串中如果内容是文本,选 择用 split 方法或者更用的 splitlines 将一个行列表由于符串到 单行是很常的需求,直接文象调用 readlines,进行更方便更快的 处理 直接文象用循语,或者将它传递给一个需要迭象的处理者, 如 list 或者 max它被做一个迭象处理时,一个被打开并被读的文 象中的每一个文本行都了迭子因,用于文本文种逐行迭 的处理方式很节省内资源,度错 在 UNIX 或者类 UNIX 系统中,如 Linux,Mac OS X,或者他 BSD 种,文本文 和进制文实并没有什别在 Windows 和老的 Macintosh 系统中,换行符 是标准的 '\n',而别是 '\r\n' 和 '\r'Python 会帮助你把些换行符转化 '\n'意味 着你打开进制文时,需要明确告诉 Python,它就会做任何转化了达 到个目的,必传递 'rb' 给 open 的第个参数在类 UNLX ,做会 有什坏处,而总是文本文和进制文是一个好惯,然在那些 并是制性的要求过些好惯会你的程序有更好的读性,更易于 理解,时能有更好的兼容性 如果确定某文本文会用什的换行符,将 open 的第个参数定 'rU', 指定通用换行符转化你自由地在 WindowsUNIX包括 Mac OS X,及 他的老 Macintosh 交换文,完全用心任何题无论你的码在什 行,种换行符都被映射 '\n' 60 第 2 open 函数产生的文象直接调用 read 方法,如解决方案中给的第一个码 段示你做的时候,你在完读的时,失去了那个文象的引 用在实践中,Python 注意到了种场时失去引用的情况,它会迅关文 然而,更好的办法然是给 open 产生的结果指定一个,你完了处理, 显式地自行关文能够确保文处于被打开状的时间量的短,使 是在 Jython,IronPython 或他种 Python 些的高垃圾回收机制 能会自动回收,像在的基于 C 的 Python ,CPython 会立刻执行回收 了确保文象使在处理过程发生错误的情况能够确关,使用 try/finally 语,是一种稳健而谨的处理方式 file普object = open('thefile.txt') try: for line in file普object: process line finally: ile普object.close( ) f 注意,要把 open 的调用放入到 try/finally 语的 try 子中是初学者很常 的错误如果在打开文的时候就发生了错误,那就没有什东西需要关,而, 没有什实质性的东西绑定到了 file普object 个,然就调用 file普object.close() 如果选择一读文的一小部,而是全部,方式就有点了面给一个 例子,一读一个进制文的 100 个节,一直读到文 file普object = open('abinfile', 'rb') try: while True: chunk = file普object.read(100) if not chunk: break do普something普with(chunk) finally: ile普object.close( ) f 给 read 方法传入一个参数 N,确保了 read 方法读 N 个节或更少,如果读 置经很接文的话抵达文时,read 返回空符串复杂的 循好被封装复用的生器generator于个例子,们能将逻辑 的一部进行封装,是因生器generator的 yield 关键被允许在 try/finally 语的 try 子中如果要抛 try/finally 语文关的保护,们 做 def read普file普by普chunks(filename, chunksize=100): file普object = open(filename, 'rb') while True: 文 61 chunk = file普object.read(chunksize) if not chunk: break yield chunk file普object.close( ) 一 read普file普by普chunks 生器完,固定长度读和处理进制文的码就 写得极简单 for chunk in read普file普by普chunks('abinfile'): do普something普with(chunk) 逐行读文本文的任务更常需文象用循语,如 for line in open('thefile.txt', 'rU'): do普something普with(line) 了 100%确保完作之没有无用的打开的文象在,将述码修改 得更加密稳固 file普object = open('thefile.txt', 'rU'): try: for line in file普object: do普something普with(line) finally: ile普object.close( ) f 更多资料 第 2.2 节Library Reference 和 Python in a Nutshell 中关于内建 open 函数和文象 的内容 2.2 写入文 感谢:Luther Blissett 任务 你想写入文本或者进制数据到文中 解决方案 面是方便的将一个长符串写入文的办法 open('thefile.txt', 'w').write(all普the普text) # 写入文本到文本文件 open('abinfile', 'wb').write(all普the普data) # 写入数据到二进制文件 过,好是给文象指定个,你就在完作之调用 close 关 62 第 2 文象如,一个文本文 file普object = open('thefile.txt', 'w') file普object.write(all普the普text) file普 bject.close( ) o 是,很多时候想写入的数据是在一个大符串中,而是在一个符串列表或 他序列中,使用 writelines 方法要望文生,个方法并局限于行 写入,而进制文和文本文都用 file普object.writelines(list普of普text普strings) open('abinfile', 'wb').writelines(list普of普data普strings) 然先把子串拼接大符串如用’’.join再调用 write 写入,或者在循 中写入,但直接调用 writelines 要面种方式快得多 论 要建一个用于写入的文,必将 open或文象的第个参数指定w 允许写入文本数据,或者wb允许写入进制数据你试图写入而是读文 的时候,你更视 2.1 节到的关文的建议有在文被确关之, 你才能确信数据被写入了磁盘,而是暂于内中的临时缓中 批将数据写入文的用,甚批文中读数据的用更常 需准备妥符串或者符串序列,然反复地调用 write 或 writelines 每 个 write 作都会在文增添新数据,紧经写入的数据你完了写 入作,调用 close 方法来关文象如果你能够一准备好有需要写入 的数据,调用 writelines 来一性完写入任务,更快更简单但如果 你每能准备好一部数据,那好批写入起一个方法,,先在 内中建立一个大的临时数据序列用于储有的数据,然用 writelines 一性 将有数据写入文,批写入明显要更好一些读和写入种作,在一 性大数据处理和批小数据处理个方面,性能和方便程度都表很大的 别 用w或wb选打开一个文准备写入数据的时候,文中原有的数据都 将被清除使在打开之迅关,得到的然是一个空文如果你想把新数据 添加在原有的数据之,用a或ab选来打开文有一些更高的 选允许你一个文时进行读和写入作,第 2.8 节中到的r+b选 ,那实是高选中常用的一个 更多资料 第 2.1 节第 2.8 节Library Reference 和 Python in a Nutshell 中关于内建 open 函数和 文象的内容 文 63 2.3 搜索和换文中的文本 感谢:Jeff Bauer, Adam Krieg 任务 需要将文中的某个符串改一个 解决方案 符串象的 replace 方法供了符串换的简单的办法面的码支持一个 特定的文或标准输入读数据,然写入一个指定的文或标准输 #!/usr/bin/env python import os, sys nargs = len(sys.argv) if not 3 <= nargs <= 5: print "usage: %s search普text replace普text [infile [outfile]]" % \ os.path.basename(sys.argv[0]) else: stext = sys.argv[1] rtext = sys.argv[2] input普file = sys.stdin output普file = sys.stdout if nargs > 3: input普file = open(sys.argv[3]) if nargs > 4: output普file = open(sys.argv[4], 'w') for s in input普file: output普file.write(s.replace(stext, rtext)) output.close( ) input.close( ) 论 本节给的解决方案非常简单,但那是精彩的地方—如果简单的东西经够用 了,什要用复杂的东西?如开始的shebangshell 头述行示,个脚本 是一个简单的脚本,而是那些被用来入的模块,它直接行在一个 shell 命行 示中脚本检查传递给它的参数确定要搜索的文本,用于的文本,输入文 默认是标准输入,输文默认是标准输然循遍历输入文中的每一行, 完了每行文本的符串换之,再写入到输文就结束了准确点说, 关了有的文 如果内裕到能够轻放入份输入文一份是原的,一份是完了之 的,因符串是能被改的,必有个拷贝,们就进一地高 度一性完有内容的处理,而用循的端计算机少都有 256MB 64 第 2 的内,处理一个 100MB 右的文并是什题,而处理超过 100MB 的 文的情况很少,们用一行语换掉那个循 outpu 普file.write(input普file.read( ).replace(stext, rtext)) t 如你看到的,简单得得了 更多资料 参看 Library Reference 和 Python in a Nutshell 中关于内建 open 函数,文象,符串 的 replace 方法 2.4 文中读指定的行 感谢:Luther Blissett 任务 你想据给的行号,文本文中读一行数据 解决方案 Python 标准 linecache 模块非常合个任务 import linecache theline = linecache.getline(thefilepath, desired普line普number) 论 个任务而言,标准的 linecache 模块是 Python 能够供的佳解决你想要 文中的某些行进行多读时,linecache 特别有用,因 linecache 会缓一些信 避免复一些作你需要缓中获得行数据时,调用模块的 clearcache 函数来释放被用作缓的内磁盘的文发生了化时,调用 checkcache, 确保缓中储的是新的信 linecache 读并缓你指定的文中的有文本,,如果文非常大,而你 需要中一行,使用 linecache 显得是那必要如果部能是你的程 序的瓶颈,使用显式的循,并将封装在一个函数中,获得度的 一些升,像 def getline(thefilepath, desired普line普number): if desired普line普number < 1: return '' for current普line普number, line in enumerate(open(thefilepath, 'rU')): if current普line普number == desired普line普number-1: return line return '' 文 65 唯一需要注意的细节是 enumerate 0 开始计数,因,既然们假 desired普line普 number 参数 1 开始计算,需要在用==较的时候减去 1 更多资料 参 Library Reference 和 Python in a Nutshell 中的 linecache 模块Perl Cookbook 8.8 2.5 计算文的行数 感谢:Luther Blissett 任务 需要计算一个文中有多少行 解决方案 于尺大的文,简单的方式是将文读放入一个行列表中,然计算列表 的长度假文路由量 thefilepath 指定,那种方式实的码如 count = len(open(thefilepath, 'rU').readlines( )) 于非常大的文,种简单的处理方式极有能会很慢,甚会失败如果你确实 心大文的题,用循来计数是一个行的办法 count = -1 for count, line in enumerate(open(thefilepath, 'rU')): pass count += 1 如果行结束标记是\n或者含有\n,就像 Windows ,们有一个更妙 的,于大文更快的方式 count = 0 thefile = open(thefilepath, 'rb') while True: buffer = thefile.read(8192*1024) if not buffer: break count += buffer.count('\n') thefile.close( ) 给 open 的‘rb’参数是必要的,如果你追求度,那没有那个参数,段码在 Windows 的行能会较慢 论 如果有外部程序供文行统计的功能,如类 UNIX 中的 wc –l,你然 66 第 2 选择使用它们如,通过 os.popen但是,如果能够实自的行计算程序,码 通常会更简单更快,更移植性于那些大小较合的文,一全部读 到内中再处理是简单的方式于种文,用 len readlines 返回的结果计算长 度获行数 如果文大到超过了用的内如,几节,于的型的个人计算机 来说,种简单的方式将慢得本无法接作系统费九牛虎之力,试图把 文内容放入虚拟内,而个过程能失败,如果交换空间耗,虚拟内 无继假在一个型的个人计算机,装有 256MB 内和无限制的虚拟磁盘, 你尝试一性读超过 1GB 或 2GB 的文,需要心个过程中能发生的错误, 过跟你使用的作系统有一些关系一些作系统在极端的高负载力处理虚 拟内会他系统脆弱得多在个场合中,用循来处理文象,如本节解决方 案示,会更好一些内建的 enumerate 函数会自行计算行数,无你用码明确指定 一读量的节,并计算中的换行符,是本节第个处理方式的思路 能是那直,而能很完美地跨,但它能是快的办法将它和 Perl Cookbook 8.2 节较一 然而,大多数时候,性能是那要如果性能的确值得考虑,你的第一感直觉往 往能告诉你程序中真耗时的码段是哪个部,实,你相信直觉— 而进行基准测试如,有个型的中等大小的 UNIX syslog 文,18MB 略多一 点,230 000 行文本 [situ@tioni nuc]$ wc nuc 231581 2312730 18508908 nuc 考虑面的基准测试框架脚本,bench.py import time def timeo(fun, n=10): start = time.clock( ) for i in xrange(n): fun( ) stend = time.clock( ) thetime = stend-start return fun.普 普name普 普, thetime import os def linecount普w( ): return int(os.popen('wc -l nuc').read( ).split( )[0]) def linecount普1( ): return len(open('nuc').readlines( )) def linecount普2( ): count = -1 for count, line in enumerate(open('nuc')): pass return count+1 def linecount普3( ): 文 67 count = 0 thefile = open('nuc', 'rb') while True: buffer = thefile.read(65536) if not buffer: break count += buffer.count('\n') return count for f in linecount普w, linecount普1, linecount普2, linecount普3: print f.普 普name普 普, f( ) for f in linecount普1, linecount普2, linecount普3: print "%s: %.2f"%timeo(f) 首先,将种方法统计行数的结果打印来,确保没有什错误或反常发生 周知,行统计任务会因一点小错而失败然,通过制和计时函数 timeo,再将 个任务都行 10 ,并察结果在一靠的老机器,的程序得如结果 [situ@tioni nuc] $ python -O bench.py linecount普w 231581 linecount普1 231581 linecount普2 231581 linecount普3 231581 linecount普1:4.84 linecount普2:4.54 linecount普3:5.02 如你的,性能差几忽略用户类辅助性任务来都感觉 10% 的性能差然而,快的方式是简单朴实地循遍历每一行的测试境是, 一老但靠的个人计算机,行着一个流行的 Linux 发行本,慢的然是更 性的逐读数据并计算换行符的方式在实践中,除非需要处理非常大的文 ,一般总会选择简单的方式本节供的第一个方法 准确地衡量码的性能要盲目使用一些复杂的方式并寄希望能高性能好的多 非常要—要到 Python 标准要门供一个模块,timeit,用来测量程序的 度建议你用 timeit,而是用自的一些测量方法,就像在做的但 是个测试方法多前就在使用了,甚 timeit 模块在 Python 标准的时间 早,,在个例子中没有用 timeit 算情有原 更多资料 Library Reference 和 Python in a Nutshell 中关于文象,内建 enumerate 函数,os.popen 及 time 和 timeit 模块Perl Cookbook 8.2 2.6 处理文中的每个词 感谢:Luther Blissett 68 第 2 任务 你想一个文中的每个词做一些处理 解决方案 完个任务的好的办法是使用循,一个用于处理行,一个处理每一行 中的每个词 for line in open(thefilepath): for word in line.split( ): dosomethingwith(word) for 语假定了词是一串非空的符,并由空符隔开和 UNIX 程序 wc 一如 果词的定有化,使用表达式如 import re re普word = re.compile(r"[\w'-]+") for line in open(thefilepath): for word in re普word.finditer(line): dosomethingwith(word.group(0)) 在例中,词被定数母,符或单引号构的序列 论 如果需要他的词的定,然需要使用的表达式外层关于文行 的循,用改 通常把迭封装迭器象是个好意,种封装很常和易于使用,如 def words普of普file(thefilepath, line普to普words=str.split): the普file = open(thefilepath): for line in the普file: for word in line普to普words(line): yield word the普file.close( ) for word in words普of普file(thefilepath): dosomethingwith(word) 个方式清有效地将部内容开一个是怎迭有的元素本例中, 指的是文中的词,一个是要每个元素做什处理一你将迭作的部封 装在一个迭器象中常常表一个生器generator,你就使用一个 for 语来完迭作了在你的程序处复地使用个迭器,而如果需要 维护码的话,你需要修改一处—迭器的定和实部,而用到处 需要修改的部种好处,类似于任何他语言中确地定和使用了函数,就 必到处复制贴码段通过 Python 的迭器,复用循制结构 文 69 通过构将循放入一个生器,们获得他个小小的增—文被显式地 确保关了,行文本被划单词的方式更通用了默认使用符串象的 split 方 法,但是给了指定他方式的能性如,如果们需要用表达式来词, words普of普file 再做一层包装 import re def words普by普re(thefilepath, repattern=r"[\w'-]+"): wre = re.compile(repattern) def line普to普words(line): for mo in wre.finditer(line): return mo.group(0) return words普of普file(thefilepath, line普to普words) ,们给了一种默认词定的表达式定,然如果有必要使用他 的词定,传入的表达式过度追求通用化是一种有害的诱惑,但是基 于经验做的一些度的通用化总是能够半功倍采用一个函数,接选的参数, 并在参数默认值时供一些合的值,是一种快而方便的实通用化的方法 更多资料 第 19 关于迭器和生器的更多内容Library Reference 和 Python in a Nutshell 中 关于文象和 re 模块Perl Cookbook 8.3 2.7 机输入/输 感谢:Luther Blissett 任务 给定一个包含很多固定长度记录的大进制文,你想读中某一条记录,而 需要逐条读记录 解决方案 一条记录相于文头部的偏移节,就是条记录的长度再乘记录的条数整 数, 0 开始计数因,直接将读置置在确的点,然读数据 如,如果每条记录长度是 48 节长,进制文中读第 7 条记录的方法如 thefile = open('somebinfile', 'rb') record普size = 48 record普number = 6 thefile.seek(record普size * record普number) buffer = thefile.read(record普size) 注意,第 7 条,就是 record普number 是 6记录条数 0 开始统计 70 第 2 论 本节的方法用于包含相长度的记录的文通常是进制的,而用于通的 文本文了表明一点,本节给的码在调用 seek 之前,给 open 函数传递了一 个rb参数指明读进制文要被读的文是作进制文被打开的, 在终关文之前,就据需要意地使用 seek 和 read 方法—一定要好 在执行 seek 之前打开文 更多资料 Library Reference 和 Python in a Nutshell 中关于文象的节Perl Cookbook 8.12 2.8 更新机文 感谢:Luther Blissett 任务 给定一个包含很多固定长度记录的大进制文,你想读中某一条记录,并修 改条记录的某些段的值,然写回到文中 解决方案 读记录,解包,执行任何需要的数据更新,然将有段新组合记录,接着 到确的置,再写入如码 import struct format普string = '8l' # 或者说,一条记录是 8 个 4 字节整数 thefile = open('somebinfile', 'r+b') record普size = struct.calcsize(format普string) record普numberb thefile.seek(record普size * record普number) buffer = thefile.read(record普size) fields = list(struct.unpack(format普string, buffer)) #进行计算,并修改相关的字段,然后: buffer = struct.pack(format普string, *fields) thefile.seek(record普size * record普number) thefile.write(buffer) thefile.close( ) 论 本节的方法用于包含相长度的记录的文通常是进制的,而用于通的 文本文而,每条记录的长度由一个结构化的格式化符串来定,如码示 一个型的格式化符串,如,8l,指明每条记录是由 8 个 4 节整数构,每个 文 71 整数都有个被指定的值而被解包到一个Python int类型的象中在个例子中, fields 量被绑定到了一个 8 个整数的列表注意,struct.unpack 返回的是一个元组 由于元组是改的,通过计算完数据更新之能新绑定 fields 量而列 表是改的,每个段据需要新绑定因,了方便,绑定 fields 的时 候们显式地要求一个列表,时确保改个列表的长度个例子而言,列 表中需要好含有 8 个整数,们利用值8l的 format普string 来打包时, struct.pack 就会抛常如果记录的长度一,本节的方法用 了迅定记录的起始地址,选择使用相置址方式,而是用计算的 record普size*record普number 偏移节 thefile.seek(-record普size, 1) 传递给 seek 方法的第个参数值1,告诉文象定到相前置的某处 处,需要回一些节,因们给了第一个参数一个负值seek 的默认址方式是 在文中用偏移量来定文开头开始计算给 seek 的第个参数传 个 0 值,显式地要求使用默认的行方式 用好在第一使用 seek 之前打开文,用调用 write 之就马关文一 确地打开了文作需要更新的进制文,是文本文,在关文之前, 据需要文进行任意更新在展示些调用是了调打开文做 机更新的关键在,时是了再醒读者,完有作之迅关文 的要性 文需要更新时允许读和写是传递给 open 的r+b个参数的意思 打开文用于读写,但并式地文内容做任何转化,因是一个进制文 于 UNIX 和类 UNIX 系统,b部并是必需的,但是了清,是值得荐 的而,某些来说,指定个部是非常关键的,如 Windows如果你准 备头开始建个文,而要反复定,读并更新一些记录,时用关 文并新打开,将 open 的第个参数指定w+b然而,的奇怪要 求来没过通常进制文是在第一建之通过wb打开,写入数 据,然关文,再用r+b新打开并进行更新的 虽然本节方法用于记录长度一的文,过,更高需求的能性在 一个单独的索引文,供了一个数据文中的每条记录的长度和偏移量的 信种续的定长记录的方式经流行了,但是在过去是很要的方式 如,们遇到的就是文本文种类型,过 XML 越来越多,数据,偶尔 会碰到记录长度固定的进制文如果你确实需要处理种带索引的续的进 制文,码会改动多,需要索引文中读记录长度 record普size 和偏移 量,并把者作参数传递给 thefile.seek,需要如本节解决方案做的那,自计 算偏移 72 第 2 更多资料 Library Reference 和 Python in a Nutshell 关于文象和 struct 模块Perl Cookbook 8.13 2.9 zip 文中读数据 感谢:Paul Prescod、Alex Martelli 任务 你想直接检查一个 zip 格式的档文中部或者有的文,时要避免将些文 展开到磁盘 解决方案 zip 文是一种流行的跨的档文Python 标准供了 zipfile 模块来简便地 种文 import zipfile z = zipfile.ZipFile("zipfile.zip", "r") for filename in z.namelist( ): print 'File:', filename, bytes = z.read(filename) print 'has', len(bytes), 'bytes' 论 Python 能直接处理 zip 文中的数据直接看到档目录中的有子,并直接 处理些数据文解决方案中给的码段能够列档文 Zipfile.zip 中 有文的和长度 zipfile 模块在能处理卷 zip 文和带有注释的 zip 文注意,要使用 r 作 标志参数,而是 rb,虽然 rb 看起来挺自然的特别是在 Windows 于 zipfile, 它的标志 open 用的打开一个文的标志一,它本认识 rbr 标志 付有的种 zip 文如果 zip 文中包含一些 Python 模块.py 或 者.pyc 文,许有一些他的数据文,把个文的路加入到 Python 的 sys.path 中,并用 import 语来入处于个 zip 文中的模块面给一个玩 示例,是一个自包含的纯粹的展示型的例子,它会凭空建一个 zip 文,并 中入一个模块,再删除文—仅仅是了向你展示如何做到一的 import zipfile, tempfile, os, sys handle, filename = tempfile.mkstemp('.zip') os.close(handle) z = zipfile.ZipFile(filename, 'w') 文 73 z.writestr('hello.py', 'def f( ): return "hello world from "+普 普file普 普\n') z.close( ) sys.path.insert(0, filename) import hello print hello.f( ) os.unlink(filename) 行个脚本会产生一些类似的输 hello world from /tmp/tmpESVzeY.zip/hello.py 除了展示 Python zip 文中入模块的能力,段码显示了怎制一个临 时文,及怎使用 writestr 方法来向 zip 文中添加一个员,你甚用先在 磁盘建个员 注意, zip 文入模块用的路会被看做是个目录在个特定的例子,路 是/tmp/tmpESVzeY.zip,过,由于们处理的是一个临时文,因每行脚本 个值都会,体决于你用的系统体地说,在模块 hello 个被入的模块 中,全局量普 普file普 普的值是/tmp/tmpESVzeY.zip/hello.pya 一种伪路,它由被看 做目录的 zip 文路加于 zip 文中的 hello.py 的相路组合而如果 zip 文中入模块,处于 zip 文中的模块的数据文将通过相路获,需要 zip 文入模块的种特性,因你无法用 open 函数来打开一个伪路 获文象要想读或者写入 zip 文中的文,必使用 Python 标准的 zipfile 模块,如解决方案给的码那 更多的关于 zip 文中入模块的资料,请参看第 16.12 节虽然本节示例要针 UNIX,但本节论中关于 zip 文入模块的信和内容在 Windows 有效 更多资料 Library Reference 和 Python in a Nutshell 中 zipfile,tempfile,os,sys 模块的资料关于 档文树的内容,参第 2.11 节更多有关 zip 文入模块的信,参第 16.12 节 2.10 处理符串中的 zip 文 感谢:Indyana Jones 任务 你的程序接收到了一个符串,内容是一个 zip 文,需要读个 zip 文中的 信 解决方案 种题,是 Python 标准的 cStringIO 模块的拿手好 74 第 2 import cStringIO, zipfile class ZipString(ZipFile): def 普 普init普 普(self, datastring): ZipFile.普 普init普 普(self, cStringIO.StringIO(datastring)) 论 总是遇到类任务—如 zip 文能来自于数据的 BLOB 段,或者来自于 网接过去把些进制数据先临时文,然用标准模块 zipfile 打开 文然,会确保完任务之删除临时文一,想到了使用标准模 块 cStringI,之就再没用过老办法了 cStringIO 模块将一串节封装起来,你像文象一中的数据 一方面,用把 cStringIO.StringIO 的实例做一个文象,向中写入数据, 得到的是一串内中的节很多处理文的 Python 模块实本检查你传递 给它们的是是一个真的文—任何像文一的象它们都接它们是在 需要的时候调用文的方法,要给些象供了相关的方法,并做了确的 反,一都会常作展示了基于签的多机制的大力量,时解释了 什好要在你的码中进行类型检查如怕的 if type(x) is y,及稍微好点 的 if isinstance(x, y)一些的模块,如 marshal,很幸地会执着要求真实 的文,但 zipfile 很通融,本节的例子说明了,有了它生活多美好 如果用的 Python 本和流的基于 C 的 Python 本称 CPython,能在 标准中到 cStringIO 模块模块前面的 c,表示它是个基于 C 的模块, 度做过化,保证能够在他兼容的 Python 实体的标准中到的本 些兼容的 Python 实体包括了产品品质的如 Jython,用 Java 实并行在 JVM 及实验性的如 pypy,用 Python 码产生机器码结果,及 IronPython,基 于 C#并行在微软的.NET CLR别心,Python 标准总会包含 StringIO 模块,它 由纯 Python 码实因能用于任何兼容的 Python 实体,并实了和 cStringIO 一的功能过没有那快,少在流的 CPython 是需略微 修改一 import 语,确保如果有 cStringIO,就入 cStringIO,如果没有,入 StringIO 作如,码能会 import zipfile try: from cStringIO import StringIO except ImportError: from StringIO import StringIO class ZipString(ZipFile): def 普 普init普 普(self, datastring): ZipFile.普 普init普 普(self, StringIO(datastring)) 经过修改,段码就在 Jython 或他的实体中作了 文 75 更多资料 Library Reference 和 Python in a Nutshell 中的 zipfile 和 cStringIO 模块Jython 的信 参 http://www.jython.org/pypy 的信参 http://codespeak.net/pypy/IronPython 的 信参 http://ironpython.com/ 2.11 将文树档到一个缩的 tar 文 感谢:Ed Gordon、Ravi Teja Bhupatiraju 任务 需要将一个文树中的有文和子目录档到一个 tar 档文,然用流行的 gzip 方式或者更高缩率的 bzip2 方式来缩 解决方案 Python 标准的 tarfile 模块直接供了种缩方式,你需在调用 tarfile. TarFile.open 建档文时,传入一个选符串指定需要的缩方式如 import tarfile, os def make普tar(folder普to普backup, dest普folder, compression='bz2'): if compression: dest普ext = '.' + compression else: dest普ext = '' arcname = os.path.basename(folder普to普backup) dest普name = '%s.tar%s' % (arcname, dest普ext) dest普path = os.path.join(dest普folder, dest普name) if compression: dest普cmp = ':' + compression else: dest普cmp = '' out = tarfile.TarFile.open(dest普path, 'w'+dest普cmp) out.add(folder普to普backup, arcname) out.close( ) return dest普path 论 给函数 make普tar 传递一个指定缩方式的参数,符串gz表明使用 gzip 缩,默认的是bz2,指定 bzip2 缩传递一个空符串’’,表明你就 需要缩你选择缩用 gzip 缩或者 bzip2 缩时,除了会影响到文扩 展.tar.tar.gz 或.tar.bz2 之外,你的选择决定了w,w:gz和w:bz2中 76 第 2 哪个符串会被作第个参数传递给 tarfile.TarFile.open 除了 open 之外,类 tarfile.TarFile 供了几种他的类方法classmethods,用 些方法生一个合的实例发 open 方便灵活,因它模式符串参数 中获指定缩方式的信然,如果确定无条地使用 bzip2 缩方式,用 类方法 bz2open 来 open 一们拥有了 tarfile.TarFile 的一个实例,并置好了们需要的缩方式, 个实例的 add 方法将完有剩的作特别是,符串 folder普to普backup 是目 录而是通文的时,add 会递地把目录中有的子树添加进来在一 些场合中,们能会希望改默认的行,并精确地制需要被档的文和目录, 们给 add 传递一个额外的参数 recursive=False 来关默认的递添加功能在 调用 add 之,留给 make普tar 函数做的情就是关 TarFile 实例并返回写入的 tar 文路,是因有时调用者会需要使用个信 更多资料 Library Reference 中关于 tarfile 模块的文档 2.12 将进制数据发到 Windows 的标准输 感谢:Hamish Lawson 任务 在 Windows ,你想把进制数据如一张图发到 stdout 中 解决方案 Python 标准中,依赖特定Windows的模块 msvcrt 供了 setmode 函数, 用来完个任务 import sys if sys.platform == "win32": import os, msvcrt msvcrt.setmode(sys.stdout.fileno( ), os.O普BINARY) 在给 sys.stdout.write 任何节或者符串参数,些节和符串会被加修 改地传递到标准输中 论 由于 UNIX 并或需要文本和进制模式,如果打算在 Windows 中读或 者写入进制数据,如图,必进制模式打开文于向标准输 文 77 如,CGI 脚本就能会写入进制数据的程序而言是个题,因 Python 通 常文本模式打开 sys.stdout 文象 指定命行选-u 进制模式打开 stdout如,如果你知道你的 CGI 脚本 行在 Apache web 服务器中,写你的脚本的第一行 #! c:/python23/python.exe –u 假了用的是 Python 2.3 的标准装过,并总能制你的脚本在什的命 行中行解决方案中给的是一个行的选择setmode 函数供了 Windows 用 的 msvcrt 模块,方便用户修改 stdout 固有的文述符通过个函数,在程 序内部确认 sys.stdout 被置进制模式 更多资料 Library Reference 和 Python in a Nutshell 中 msvcrt 模块的文档 2.13 使用 C++的类 iostream 语法 感谢:Erik Max Francis 任务 你喜爱 C++的基于 ostream 和纵符插入了种特定的象,它会在 stream 中产生 特定的效果的 I/O 方式,并想将形式用在自的 Python 程序中 解决方案 Python 允许使用特殊方法前带有续个划线的方法进行了定 的类来载原有的作符了将<<用于输,如在 C++中做的一,需要编写 一个输流类,并定特殊方法普 普lshift普 普 class IOManipulator(object): def 普 普init普 普(self, function=None): self.function = function def do(self, output): self.function(output) def do普endl(stream): stream.output.write('\n') stream.output.flush( ) endl = IOManipulator(do普endl) class OStream(object): def 普 普init普 普(self, output=None): if output is None: import sys output = sys.stdout 78 第 2 self.output = output self.format = '%s' def 普 普lshift普 普(self, thing): ''' 当你使用 <<操纵符并且左边操作对象是 OStream 时, Python 会调用这个特殊方法 ''' if isinstance(thing, IOManipulator): thing.do(self) else: self.output.write(self.format % thing) self.format = '%s' return self def example普main( ): cout = OStream( ) cout<< "The average of " << 1 << " and " << 3 << " is " << (1+3)/2 <>somewhere, "The average of %d and %d is %f\n" % (1, 3, (1+3)/2) 种方式是 Python原生的方式看去很像 C 的风格要看你更惯 C++是 C,少本节给了你一个选择使终没有使用本节供的方式,了解 Python 中 简单的作符载是蛮有趣的 更多资料 Library Reference 和 Python in a Nutshell 中关于文象及特殊方法普 普lshift普 普的文 档第 4.20 节中关于 C 函数 printf 的 Python 实的信 文 79 2.14 回输入文到起点 感谢:Andrew Dalke 任务 需要建一个输入文象数据能来自于网 socket 或者他输入文柄, 文象允许回到起点,就完全读中有数据 解决方案 将文象封装到一个合的类中 from cStringIO import StringIO class RewindableFile(object): """ 封装一个文件句柄以便重定位到开始位置 """ def 普 普init普 普(self, input普file): """ 将 input普file 封装到一个支持回退的类文件对象中 """ self.file = input普file self.buffer普file = StringIO( ) self.at普start = True try: self.start = input普file.tell( ) except (IOError, AttributeError): self.start = 0 self.普use普buffer = True def seek(self, offset, whence=0): """ 根据给定的字节定位 . 必须: whence == 0 and offset == self.start """ if whence != 0: raise ValueError("whence=%r; expecting 0" % (whence,)) if offset != self.start: raise ValueError("offset=%r; expecting %s" % (offset, self.start)) self.rewind( ) def rewind(self): """ 回到起始位置 """ self.buffer普file.seek(0) self.at普start = True def tell(self): """ 返回文件的当前位置必须在开始处 """ if not self.at普start: raise TypeError("RewindableFile can't tell except at start of file") return self.start def 普read(self, size): 80 第 2 if size < 0: # 一直读到文件末尾 y = self.file.read( ) if self.普use普buffer: self.buffer普file.write(y) return self.buffer普file.read( ) + y elif size == 0: # 不必读空字符串 return "" x = self.buffer普file.read(size) if len(x) < size: y = self.file.read(size - len(x)) if self.普use普buffer: self.buffer普file.write(y) return x + y return x def read(self, size=-1): """ 根据 size 指定的大小读取数据 默认为 -1,意味着一直读到文件结束 """ x = self.普read(size) if self.at普start and x: self.at普start = False self.普check普no普buffer( ) return x def readline(self): """ 从文件中读取一行 """ # buffer普file 中有吗? s = self.buffer普file.readline( ) if s[-1:] == "\n": return s # 没有,从输入文件中读取一行 t = self.file.readline( ) if self.普use普buffer: self.buffer普file.write(t) self.普check普no普buffer( ) return s + t def readlines(self): """读取文件中所有剩余的行 """ return self.read( ).splitlines(True) def 普check普no普buffer(self): # 如果’nobuffer’被调用,而且我们也完成了对缓存文件的处理 # 那就删掉缓存,把所有的东西都重定向到原来的输入文件 if not self.普use普buffer and \ self.buffer普file.tell()==len(self.buffer普file. getvalue()): # 为了获得尽可能高的性能,我们重新绑定了 self 中的所有相关方法 for n in 'seek tell read readline readlines'.split( ): setattr(self, n, getattr(self.file, n, None)) del self.buffer普file def nobuffer(self): """通知 RewindableFile,一旦缓存耗尽就停止继续使用缓存 """ self.普use普buffer = False 文 81 论 有时, socket 或他输入文柄中得来的数据并是们想要的如,假 一个有题的服务器读数据,服务器返回 XML 流,但它有时给你格式 化的错误信种情况时常发生,因很多服务器并能确处理错误输入 本节的 RewindableFile 类能够帮助你解决类题r = RewindableFile(f)将原来的输入 流 f 封装进了一个回的文的实例 r,r 模仿 f 的行,但时供了缓 r 的读请求被转移到了 f,读的数据添加进了缓,然返回给调用者缓中 保着到目前读的有数据 r 回,定到开始置一个请求读的内容能会来自于缓,直到缓 被全部读,时它会输入流中获数据新读的数据被添加到缓中 如果再需要缓了,直接调用 r 的 nobuffer 方法相于告诉 r,一它读完了 缓中的前内容,它就丢缓调用 nobuffer 之,seek 的行就处于定 状了 举个例子,假有个服务器,它能会给你错误信,形式ERROR: cannot do that,或者给你一个 XML 数据流,内容' % sys.argv[0] sys.exit(1) matches = list(all普files(sys.argv[1], os.environ['PATH'])) print '%d match:' % len(matches) for match in matches: print match 更多资料 第 2.18 节中的那个更简单的据搜索路文的例子 Library Reference 和 Python in a Nutshell 中的 os 和 glob 的相关文档 2.20 在 Python 的搜索路中文 感谢:Mitch Chapman 任务 一个大的 Python 用程序包括了资源文如 Glade 目文SQL 模和图 88 第 2 及 Python 包Python package你想把有些相关文和用到它们的 Python 包储 起来 解决方案 在 Python 的 sys.path 中文或目录 import sys, os class Error(Exception): pass def 普find(pathname, matchFunc=os.path.isfile): for dirname in sys.path: candidate = os.path.join(dirname, pathname) if matchFunc(candidate): return candidate raise Error("Can't find file %s" % pathname) def findFile(pathname): return 普find(pathname) def findDir(path): return 普find(path, matchFunc=os.path.isdir) 论 较大的 Python 用程序由一系列 Python 包和相关的资源文组将些相关文 和用到它们的 Python 包一起储起来是很方便的,很容易地 2.18 供的码 略加修改,使之能据 Python 搜索路的相路来文和目录 更多资料 2.18 节Library Reference 和 Python in a Nutshell 中的 os 模块相关内容 2.21 动地改 Python 搜索路 感谢:Robin Parmar 任务 模块必处于 Python 搜索路中才能被入,但你想置个永久性的大路,因 那能会影响性能,,你希望能够动地改个路 解决方案 需简单地在 Python 的 sys.path 中加入一个目录,过要小心复的情况 def AddSysPath(new普path): """ AddSysPath(new普path):给 Python 的 sys.path 增加一个“目录” 如果此目录不存在或者已经在 sys.path 中了,则不操作 文 89 返回 1 表示成功,-1 表示 new普path 不存在,0 表示已经在 sys.path 中了 already on sys.path. """ import sys, os # 避免加入一个不存在的目录 if not os.path.exists(new普path): return -1 # 将路径标准化。 Windows 是大小写不敏感的,所以若确定在 # Windows 下,将其转成小写 new普path = os.path.abspath(new普path) if sys.platform == 'win32': new普path = new普path.lower( ) # 检查当前所有的路径 for x in sys.path: x = os.path.abspath(x) if sys.platform == 'win32': x = x.lower( ) if new普path in (x, x + os.sep): return 0 sys.path.append(new普path) # 如果想让 new普path 在 sys.path 处于最前 # 使用:sys.path.insert(0, new普path) return 1 if 普 普name普 普 == '普 普main普 普': # 测试,显示用法 import sys print 'Before:' for x in sys.path: print x if sys.platform == 'win32': print AddSysPath('c:\\Temp') print AddSysPath('c:\\temp') else: print AddSysPath('/usr/lib/my普modules') print 'After:' for x in sys.path: print x 论 模块要处于 Python 搜索路中的目录才能被入,但们喜维护一个永久性的 大目录,因他有的 Python 脚本和用程序入模块的时候性能都会被拖累本 节码动地在路中添加了一个目录,然前是目录在而前在 sys.path 中 sys.path 是个列表,在添加目录是很容易的,用 sys.path.append 就行了 个 append 执行完之,新目录时起效,的每 import 作都能会检查个目 录如解决方案示,选择用 sys.path.insert(0,…,新添加的目录会先于 他目录被 import 检查 90 第 2 使 sys.path 中在复,或者一个在的目录被小心添加进来,没什大 了,Python 的 import 语非常聪明,它会自付类题但是,如果每 import 时都发生种错误如,复的功搜索,作系统示的需要进一处理的错 误,们会被迫付一点小小的性能了避免种无谓的开,本节码在 向 sys.path 添加内容时非常谨慎,加入在的目录或者复的目录程序向 sys.path 添加的目录会在程序的生命周期之内有效,他有的 sys.path 的动 作是如 更多资料 Library Reference 和 Python in a Nutshell 中 sys 和 os.path 模块的内容 2.22 计算目录间的相路 感谢:Cimarron Taylor、Alan Ezust 任务 需要知道一个目录一个目录的相路是什—如,有时需要建一个符号 链接或者一个相的 URL 引用 解决方案 简单的方法是把目录到一个目录的列表中,然列表进行处理们需要用 到一些辅助函数和助手函数,码如 import os, itertools def all普equal(elements): ''' 若所有元素都相等,则返回 True,否则返回 False''' first普element = elements[0] for other普element in elements[1:]: if other普element != first普element: return False return True def common普prefix(*sequences): ''' 返回所有序列开头部分共同元素的列表 紧接一个各序列的不同尾部的列表 ''' # 如果没有 sequence,完成 if not sequences: return [ ], [ ] # 并行地循环序列 common = [ ] for elements in itertools.izip(*sequences): # 若所有元素相等,跳出循环 if not all普equal(elements): break # 得到一个共同的元素,添加到末尾并继续 common.append(elements[0]) 文 91 # 返回相同的头部和各自不同的尾部 return common, [ sequence[len(common):] for sequence in sequences ] def relpath(p1, p2, sep=os.path.sep, pardir=os.path.pardir): ''' 返回 p1 对 p2 的相对路径 特殊情况:空串, if p1 == p2; p2,如果 p2 和 p1 完全没有相同的元素 ''' common, (u1, u2) = common普prefix(p1.split(sep), p2.split(sep)) if not common: return p2 # 如果完全没有共同元素,则路径是绝对路径 return sep.join( [pardir]*len(u1) + u2 ) def test(p1, p2, sep=os.path.sep): ''' 调用 relpath 函数,打印调用参数和结果 ''' print "from", p1, "to", p2, " -> ", relpath(p1, p2, sep) if 普 普name普 普 == '普 普main普 普': test('/a/b/c/d', '/a/b/c1/d1', '/') test('/a/b/c/d', '/a/b/c/d', '/') test('c:/x/y/z', 'd:/x/y/z', '/') 论 本节解决方案给的码中,简单而通用的 common普prefix 是关键部,给它任意 N 个序列,它能返回 N 个序列共的头部,和一个相的部列表了计算个 目录之间的相路,忽略掉它们的共头部们需要一定数目的向一 标记通常用 os.path.pardir,如类 UNIX 系统中的../需要和部长度相数目 的种符号,然再添目标目录的部relpath 函数将一个完整路一个目录 的列表,然调用 common普prefix,接着执行们才述过的作 common普prefix 的心部在那个循,for elements in itertools.izip(*sequences),它依 赖个实短的序列循到头时,izip 结束了循的体部必在遇到一 个全相等的元组据 izip 的说明,每个元素都来自序列时立刻结束,时 要在个过程中把全等的元素放进 common 列表中保起来一循结束,剩要 做的情就是据 common 列表,把个序列的相头部全部掉 all普equal 函数能用一种方式实,没那简洁明快,但是很有趣 def all普equal(elements): return len(dict.fromkeys(elements)) == 1 或者,等但更简洁一点,用于 Python 2.4 def all普equal(elements): return len(set(elements)) == 1 有元素相等,等于包含包含些元素的集合的势cardinality 1在使用 dict.fromkeys 的体中,用 dict 来 set,那个例子时用于 Python 2.3 和 2.4用 set 的那个例子更清,但能在 Python 2.4 的本中使用用 Python 92 第 2 标准中的 sets 模块来改写,它时用于 Python 2.3 更多资料 Library Reference 和 Python in a Nutshell 中的 os 和 itertools 模块 2.23 跨地读无缓的符 感谢:Danny Yoo 任务 你的程序需要标准输中,读无缓的单个的符,而它必能够时在 Windows 和类 UNIX 系统中作 解决方案 们手的是依赖性的,而需求是无关的时候,试试将些差 封装起来 try: from msvcrt import getch except ImportError: ''' 我们不在 Windows 中,所以可以尝试类 UNIX 的方式 ''' def getch( ): import sys, tty, termios fd = sys.stdin.fileno( ) old普settings = termios.tcgetattr(fd) try: tty.setraw(fd) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old普settings) return ch 论 在 Windows 中,Python 标准模块 msvcrt 供了方便的 getch 函数来读无缓的单 个符,直接键盘读,会在屏幕回显但是个模块并是 UNIX 和类 UNIX 中 Python 标准的一部,如 Linux 和 Mac OS X 的 Python 标准中就供 个模块在些中,能利用 Python 标准的 tty 和 termios 模块的, 些模块在 Windows 中供功能来实类似的功能 在用的程序码中,们几来考虑种题们更倾向于将程序写 得更加无关,并依赖函数来实在系统间的跨越Python 标准于大多 文 93 数任务的跨能力都供了极秀的支持,但本节任务中的需求,好是 Python 标准没有供相的跨解决方案的一个例子 们能在标准中到用的跨包或时,自法打包,并作自的 加自定的一部本节的解决方案,仅解决了一个特别的任务,时展示了一 种好的通用封装方式选择测试 sys.platform,但更喜本节给的方法 你自的模块在特定系统入标准模块时,尝试用 Try 语,包括的 except ImportError 语,行的系统符合要求时 except ImportError 会被激发在 except 语中,你的模块选择任何他在前系统作的方法在一些 较少的情况,你能会需要种的相关的方法,但大多数情况,你 都需准备一个在 Windows 作的方法和一个用于有他的方法是 因的大多数非 Windows 基本都是 UNIX 或者类 UNIX 更多资料 Library Reference 和 Python in a Nutshell 中 msvcrt,tty 和 termios 的内容 2.24 在 Mac OS X 统计 PDF 文档的页数 感谢:Dinu Gherman、Dan Wolfe 任务 你的计算机行着较新的 Mac OS X 系统10.3 的Panther或更新的本,在 需要知道一个 PDF 文档的页数 解决方案 PDF 格式和 Python 都经集到了 Mac OS X 系统中10.3 或更高本,因而个 题解决起来相较容易 #!/usr/bin python import CoreGraphics def pageCount(pdfPath): " 返回指定路径的 PDF 文档的页数 " pdf = CoreGraphics.CGPDFDocumentCreateWithProvider( CoreGraphics.CGDataProviderCreateWithFilename(pdfPath) ) return pdf.getNumberOfPages( ) if 普 普name普 普 == '普 普main普 普': import sys for path in sys.argv[1:]: print pageCount(path) 94 第 2 论 一个完任务的方法是使用 Python 扩展,PyObjC,它使得 Python 码利用 Mac OS X 带的 Foundation 和 AppKit 框架的能力方案你的码行在较老 本的 Mac OS X 中,如 10.2 Jaguar过依赖并使用 Mac OS X 10.3 或更高本 供的集 Python 的境和 CoreGraphics 扩展是 Mac OS X Panther的一部, 使们的码直接利用 Apple 大的 Quartz 图形引 更多资料 关于 PyObjC,参看 http://pyobjc.sourceforge.net/更多有关 CoreGraphics 模块的资料 请 http://www.macdevcenter.com/pub/a/mac/2004/03/19/core普graphics.html 2.25 在 Windows 修改文属性 感谢:John Nielsen 任务 需要修改 Windows 一系列文的属性,如将某些文置读档等 解决方案 PyWin32 的 win32api 模块供了一个 SetFileAttributes 函数,好用来完种 任务 import win32con, win32api, os # 创建一个文件,并展示如何操纵它 thefile = 'test' f = open('test', 'w') f.close( ) # 设置成隐藏文件 ...: win32api.SetFileAttributes(thefile, win32con.FILE普ATTRIBUTE普HIDDEN) # 设置成只读文件 win32api.SetFileAttributes(thefile, win32con.FILE普ATTRIBUTE普READONLY) # 为了删除它先把它设成普通文件 win32api.SetFileAttributes(thefile, win32con.FILE普ATTRIBUTE普NORMAL) # 最后删掉该文件 os.remove(thefile) 论 win32api.SetFileAttributes 的一个有趣的用法是用来删除文用 os.remove 在 Windows 中删除非通文会遭遇失败了删除文,必先通过 Win32 调用 SetFileAttributes 文 95 把文的属性置通,如本节码一段展示的那然,做 能会遇到警告,因一个文没有被置通文通常是有很好的理由的有 你很明确并很有把握的时候,才真的删掉一个文 更多资料 查看于 http://ASPN.ActiveState.com/ASPN/Python/Reference/Products/ActivePython/ PythonWin32Extensions/win32file.html 的 win32file 模块文档 2.26 OpenOffice.org 文档中文本 感谢:Dirk Holtwick 任务 需要 OpenOffice.org 文档的文本内容无论有无 XML 标记中抽数据 解决方案 OpenOffice.org 文档实就是一个聚合了 XML 文的 zip 文,遵循一种良好的文档 规范如果是了中的数据,们甚用装 OpenOffice.org import zipfile, re rx普stripxml = re.compile("<[^>]*?>", re.DOTALL|re.MULTILINE) def convert普OO(filename, want普text=True): """ 将一个 OpenOffice.org 文件转换成 XML 或文本 """ zf = zipfile.ZipFile(filename, "r") data = zf.read("content.xml") zf.close( ) if want普text: data = " ".join(rx普stripxml.sub(" ", data).split( )) return data if 普 普name普 普=="普 普main普 普": import sys if len(sys.argv)>1: for docname in sys.argv[1:]: print 'Text of', docname, ':' print convert普OO(docname) print 'XML of', docname, ':' print convert普OO(docname, want普text=False) else: print 'Call with paths to OO.o doc files to see Text and XML forms.' 论 OpenOffice.org 文档就是 zip 文,并包含了一些他内容,中一般都会有 content.xml 96 第 2 文本节的任务实就是 zip 文中抽数据本节的方法完全抛开了 XML 标记,是用了一个简单的表达式,用空符将内容开,然在段间插入一 个空格并合并起来,节省空间然,们利用 XML 解析器,更结构 化的手段来获信,但是们需要的是一些文本内容,种快放的 方法经满足需要了 特别指一点,表达式 rx普stripxml 配的是 XML 标签开始和结束的起始符< 和结束符>在函数 convert普OO 中,在 if want普text 语之,们用个表达式 将有的 XML 标签换空格,然据空符进行调用符串方法 split,能 够割任何空符序列,之再合并使用 " ".join,用一个空格符作接串种 割再合并的方法,本质是把任何空符序列都一个空格符更多的有关 XML 文档中文本的内容参看第 12.3 节 更多资料 Library Reference 文档中的 zipfile 和 re 模块OpenOffice.org 的页 http://www.openoffice. org/12.3 2.27 微软 Word 文档中抽文本 感谢:Simon Brunning、Pavel Kosina 任务 你想 Windows 某个目录树中的个微软 Word 文中抽文本,并保 的文本文 解决方案 借助 PyWin32 扩展,通过 COM 机制,利用 Word 来完转换 import fnmatch, os, sys, win32com.client wordapp = win32com.client.gencache.EnsureDispatch("Word.Application") try: for path, dirs, files in os.walk(sys.argv[1]): for filename in files: if not fnmatch.fnmatch(filename, '*.doc'): continue doc = os.path.abspath(os.path.join(path, filename)) print "processing %s" % doc wordapp.Documents.Open(doc) docastxt = doc[:-3] + 'txt' wordapp.ActiveDocument.SaveAs(docastxt, FileFormat=win32com.client.constants.wdFormatText) wordapp.ActiveDocument.Close( ) 文 97 finally: # 确保即使有异常发生 Word 仍能被正常关闭 wordapp.Quit( ) 论 关于 Windows 用程序的一个有趣的地方是,通过 COM 及 Python 供的 PyWin32 扩展,编写一些简单的脚本些用程序进行制个扩展允许你用 Python 脚本来完种 Windows 的任务本节的脚本,目录树的有的 Word 文档.doc 文中抽文本,并的.txt 文本文通过使用 os.walk 函数, 并利用 for 循语,们无递遍历树中的有子目录通过 fnmatch.fnmatch 函数,检查文确认它是符合们给的通配符,的通配符是.doc 一们确认了是一个 Word 文档,们就用文和 os.path 来得到一个路 ,再用 Word 打开它,文本文,然关 如果没有装 Word ,能需要完全的方法来达目标一种能是使用 OpenOffice.org,它载入 Word 文档一种能是使用读 Word 文档的程 序,如 Antiword,网址是 http://www.winfield.demon.nl/但准备探种 方式 更多资料 Mark HammondAndy Robinson 著的 Python Programming on Win32O’Reilly一 书 中 关于 PyWin32 的绍http://msdn.microsoft.com 绍了微软 Word 的象模型Library Reference 和 Python in a Nutshell 中关于 fnmatch 和 os.path 及 os.walk 的相关节 2.28 使用跨的文 感谢:Jonathan Feinberg、John Nielsen 任务 希望某个能时行在 Windows 和类 UNIX 的程序有文的能力,但 Python 标准供的定文的方法是相关的 解决方案 如果 Python 标准没有供合的跨解决方案,们自实一个 import os # 需要 win32all 来工作在 Windows 下NT、2K、XP、不包括 9x if os.name == 'nt': import win32con, win32file, pywintypes 98 第 2 LOCK普EX = win32con.LOCKFILE普EXCLUSIVE普LOCK LOCK普SH = 0 # 默认 LOCK普NB = win32con.LOCKFILE普FAIL普IMMEDIATELY 普 普overlapped = pywintypes.OVERLAPPED( ) def lock(file, flags): hfile = win32file.普get普osfhandle(file.fileno( )) win32file.LockFileEx(hfile, flags, 0, 0xffff0000, 普 普overlapped) def unlock(file): hfile = win32file.普get普osfhandle(file.fileno( )) win32file.UnlockFileEx(hfile, 0, 0xffff0000, 普 普overlapped) elif os.name == 'posix': from fcntl import LOCK普EX, LOCK普SH, LOCK普NB def lock(file, flags): fcntl.flock(file.fileno( ), flags) def unlock(file): fcntl.flock(file.fileno( ), fcntl.LOCK普UN) else: aise RuntimeError("PortaLocker only defined for nt and posix platforms") r 论 很多程序或线程需要一个共享的文时,确保有的是的, 就会个进程或线程时修改文内容的情况失败的在某些情况 会完全破坏掉整个文 本节码给了个函数,lock 和 unlock,别用于请求和释放一个文的 portallocker.py 模块的使用实就是简单地调用 lock 函数,传递一个文给它,再用一 个参数来指定需要的的类型 Shared lock 默认 种会拒有进程的写入请求,包括初定的进程但有的进程都 读被定的文 Exclusive lock 拒他有进程的读和写入的请求 Nonblocking lock 个值被指定时,如果函数能获指定的会立刻返回,函数会处于等 状使用 Python 的作符,或作|,将 LOCK普NB 和 LOCK普SH 或 LOCK普EX 进行或作 举个例子 import portalocker afile = open("somefile", "r+") portalocker.lock(afile, portalocker.LOCK普EX) 文 99 在系统中 lock 和 unlock 的实完全类 UNIX 系统包括 Linux 和 Mac OS X 中,本节码的功能依赖于标准的 fcntl 模块在 Windows 系统中NT2000XP Win95 和 Win98 用,因它们并没有供相的层支持,使用 win32file 模 块,模块是 Windows 量身定做的流行的 PyWin32 扩展的一个组部,PyWin32 的作者是 Mark Hammond点是管内部实如何,个函数包括需要传 递给 lock 函数的标志在的表得完全一种基于包的实但 表功能一性的方法,有助于写跨的用程序,是 Python 的力量 在 写跨的程序时,好是将种功能无关的方式封装起来如本节的文 部,于 Perl 用户是很有帮助的,他们很惯直接使用系统调用 lock更遍 的是,虽然 if os.name==属于用层面的码,但是类测试码总是被 插到标准或者用无关的模块中 更多资料 Library Reference 中 fcntl 模块的文档http://ASPN.ActiveState.com/ASPN/Python/ Reference/Products/ActivePython/PythonWin32Extensions/win32file.html 中有关 win32file 模块的绍Jonathan Feinberg 的页http://MrFeinberg.com 2.29 带本号的文 感谢:Robin Parmar、Martin Miller 任务 如果你想在改写某文之前做个备份,在老文的面据惯例加 个数的本号 解决方案 们需要编写一个函数来完备份作 def VersionFile(file普spec, vtype='copy'): import os, shutil if os.path.isfile(file普spec): # 检查'vtype'参数 if vtype not in ('copy', 'rename'): raise ValueError, 'Unknown vtype %r' % (vtype,) # 确定根文件名,所以扩展名不会太长 n, e = os.path.splitext(file普spec) # 是不是一个以点为前导的三个数字? if len(e) == 4 and e[1:].isdigit( ): 100 第 2 num = 1 + int(e[1:]) root = n else: num = 0 root = file普spec # 寻找下一个可用的文件版本 for i in xrange(num, 1000): new普file = '%s.%03d' % (root, i) if not os.path.exists(new普file): if vtype == 'copy': shutil.copy(file普spec, new普file) else: os.rename(file普spec, new普file) return True raise RuntimeError,"Can't%s%r,all names taken"%(vtype,file普spec) return False if 普 普name普 普 == '普 普main普 普': import os # 创建一个 test.txt 文件 tfn = 'test.txt' open(tfn, 'w').close( ) # 对它取 3 次版本 print VersionFile(tfn) # 输出: True print VersionFile(tfn) # 输出: True print VersionFile(tfn) # 输出: True # 删除我们刚刚生成的 test.txt*文件 for x in ('', '.000', '.001', '.002'): os.unlink(tfn + x) # 展示当文件不存在时取版本操作的结果 print VersionFile(tfn) # 输出: False print VersionFile(tfn) # 输出: False 论 VersionFile 函数是了确保在打开文进行写入或更新等修改前,在的目标文 完了备份或者命,由选的第个参数来决定在处理文之前进行备份是 很明智的举措是一些人然念的 VMS 作系统的原因,种备份是自动进 行的实的复制和命是别由 shutil.copy 和 os.rename 完的,唯一的 题是,怎确定文的 一个流行的决定备份的的方法是使之本化如,给文增加一个逐渐增大 文 101 的数本节确定文的方法是,首先文中解因有能 经是一个本化的文了,然在个之添加进一的扩展, 如.000,.001,等等,直到种命方式确定的文无法任何一个在的文 注意,VersionFile 被限制能有 1 000 个本,需要有个档备份的计划 在进行本化之前,首先要确保文在—能在的东西进行备份如果 文在,VersionFile 函数是简单地返回 False如果文在而函数执行无误 返回 True,在调用之前无检查文是在 更多资料 Library Reference 和 Python in a Nutshell 中关于 os 和 shutil 模块的文档 2.30 计算 CRC-64 循冗余码校验 感谢:Gian Paolo Ciceri 任务 需要某些数据进行循冗余码校验CRC确定数据的完整无误,而必遵循 ISO-3309 关于 CRC-64 校验的规定 解决方案 Python 标准并没有供 CRC-64 的任何实但供了 CRC-32 函数, zlib.crc32, 们需要自来供实幸而 Python 能够处理作或非或移 等,就像 C 那实,它们的语法几完全相,很容易据 CRC-64 的参考实写如的 Python 函数 # 使用两个辅助表为了速度我们用了一个函数, # 之后删除该函数,因为我们再也用不着它了: CRCTableh = [0] * 256 CRCTablel = [0] * 256 def 普inittables(CRCTableh, CRCTablel, POLY64REVh, BIT普TOGGLE): for i in xrange(256): partl = i parth = 0L for j in xrange(8): rflag = partl & 1L partl >>= 1L if parth & 1: partl ^= BIT普TOGGLE parth >>= 1L if rflag: parth ^= POLY64REVh 102 第 2 CRCTableh[i] = parth CRCTablel[i] = partl # CRC64 的高 32 位的生成多项式低 32 位被假设为 0 # 以及普inittables 所用的 bit-toggle 掩码 POLY64REVh = 0xd8000000L BIT普TOGGLE = 1L << 31L # 运行函数来准备表 普inittables(CRCTableh, CRCTablel, POLY64REVh, BIT普TOGGLE) # 删除我们不需要的名字,包括生成表的函数 del 普inittables, POLY64REVh, BIT普TOGGLE # 此模块公开了这两个函数: crc64 和 crc64digest def crc64(bytes, (crch, crcl)=(0,0)): for byte in bytes: shr = (crch & 0xFF) << 24 temp1h = crch >> 8L temp1l = (crcl >> 8L) | shr tableindex = (crcl ^ ord(byte)) & 0xFF crch = temp1h ^ CRCTableh[tableindex] crcl = temp1l ^ CRCTablel[tableindex] return crch, crcl def crc64digest(aString): return "%08X%08X" % (crc64(bytes)) if 普 普name普 普 == '普 普main普 普': # 当此模块作为主脚本运行时,一个小测试 /展示 assert crc64("IHATEMATH") == (3822890454, 2600578513) assert crc64digest("IHATEMATH") == "E3DCADD69B01ADD1" print 'crc64: dumb test successful' 论 循冗余码校验CRC是一种流行的确保数据例如,文被损坏的方法CRC 稳定地检查一些机偶发的损坏,但是于恶意的针性攻并像他一些 加密的校验和方法那劲CRC 的计算他校验和方式都要快,因在那些需 要检测偶发和机损坏,而用心别人故意伪数据进行欺骗的情况,它他 校验方式用得更多 数学的角度讲,CRC 是把需要校验和的数据的做多式进行计算的实, 如本节码示,经过确的索引处理,计算一完并将结果储在表中, 数据中的每一个节都终的结果产生影响,在初始化之们用一个辅 助函数来初始化,是因在 Python 中使用局部量进行计算要使用全局量计算 快得多,CRC 计算的度非常快表的计算和相关的处理用到了很多作,过, 幸的是,Python 类作的处理效果和他语言一,如 C,度非常快实 Python 关于作的语法和 C 完全一 文 103 个标准的 CRC-64 校验和的算法在 ISO-3309 标准中有细说明,本节的实方法完 全地遵照了标准的述生器多式是 x64 + x4 + x3 + x + 1在更多资料小 节中会供更多的关于计算方法的信 用一 Python 的 int 类型来储 64 的结果,别表高 32 和 32 了能 够递进地计算 CRC—有时数据能是逐到达的,给 crc64 调用函数供了一个 选的初始值,(crch, crcl)构的数,在前一计算结果的基础继续计 算如果要一计算全部数据的 CRC,需要供完整的数据节的序列,如本 节中一,时数会被默认地置(0, 0) 更多资料 W.H. Press,S.A. Teukolsky,W.T. Vetterling,及 B.P. Flannery 著的 Numerical Recipes in C, 2d ed. (Cambridge University Press), pp. 896ff 104 第 2 第 3 时间和务计算 引言 感谢:Gustavo Niemeyer、Facundo Batista 周明些词听来是如的普通你能会想知道,们的生活时 间念的联多深于时间的概念无处在,然,它们在大多数的 中所体使是一些非常简单的程序都能时间,如时间戳延超 时度测量历等了满足通用程序的类需求,Python 标准供了坚实的 础支持,而更多的他支持则来自于第方模块和包 到务的计算则是一个引人注意的趣题,因它们的常生活联系非 常紧密Python 2.4 引入了对十制数的支持然在 Python 2.3 中引入,参 看 http://www.taniquetil.com.ar/facundo/bdvfiles/get轮decimal.html,使得用户避免 使用制浮点数,使得 Python 了执行类计算的一个很好的选择 本将覆盖着个题,务和时间们许说一实一个题, 大家都知道一老,时间就是金钱 时间模块 Python 标准的时间模块使得 Python 用程序利用和借助行供的各种 时间相的功能因,在你的中,供等功能的 C 文档能会很用, 而,在一些较奇特的,Python 能会到自身的一些影响 时间模块中常用的一个函数就是获前时间的函数 time.time在初始化的情况 返回值看来实在是够直一个浮点数,表了某个特定时间点质被 纪元epoch质开始所经历的数,个时间点根据的能会些 ,但通常都是 1970 1 1 夜 国国 105 要检查所使用的纪元,在 Python 交互式解释器的示符输入面语 >>> import time >>> print time.asctime(time.gmtime(0)) 注意,们给 time.gmtime 函数传递了参数 0表示纪元之 0 开始time.gmtime 将任何时间戳纪元开始所经历的数转化一个元,元表了人类容易 理解的一种时间格式,而无行任何时转化GMT 表了格林威治标准时间, 就是大家通常所知的 UTC,世界标准时间的一种说法传递一个时间 戳纪元开始所经历的数给 time.localtime,它会根据前时行时间转化 理解个别很要,如果获得一个经根据地时间行调整了的时间戳,将传 递给 time.localtime 函数,将会获得一个预期的结果质除非非常走,你的前时 好就是 UTC 时 给出一个返回的元中获前本地时间的方法 year, month, mday, hour, minute, second, wday, yday = time.localtime( ) 虽然码能行,但是是很优,好要经常用完全避免用种 方式获时间,因 time 函数返回的元供了意的属性,更加易于使用 如,获前份,就写简洁而优的一行 time.localtime( ).tm轮mon 注意,们忽略了传递给 localtime 的参数调用 localtimegmtime 或者 asctime 而 供参数时,默认使用前时间 时间模块中个非常用的函数是 strftime质它根据返回的时间元构建一个 符串, strptime质前者完全相反,它将解析给定的符串并产生一个时间元 个函数都接一个格式化符串,指明真感趣的部或者你希望 符串中解析出的部于传递给个函数的格式符串的更多信和细节参看 http://docs.python.org/lib/module-time.html 时间模块中一个要的函数是 time.sleep,它使得在 Python 程序中实延时 虽然个函数的 POSIX 对本接一个整数参数,Python 的本则支持一个浮点 数,并允许非整数的延时如 for i in range(10): time.sleep(0.5) print "Tick!" 段码大概耗时 5s,并断打印出Tick!,大每 时间和期对象 除了非常用的时间模块,Python 标准时引入了 datetime 模块,模块供了 106 第 3 能更好地对于抽象的期和时间的各种类型,如 timedate 和 datetime 类型构 些类型实例的码非常优和简单 today = datetime.date.today( ) birthday = datetime.date(1977, 5, 4) #5 月 4 日 currenttime = datetime.datetime.now( ).time( ) lunchtime = datetime.time(12, 00) now = datetime.datetime.now( ) epoch = datetime.datetime(1970, 1, 1) meeting = datetime.datetime(2005, 8, 3, 15, 30) 更一,如们所料,通过属性和方法,些类型供了很方便的获和操作信 的方法面的码创建了一个 date 类型,表前期,然获得一的样 的期,将结果打印出来 today = datetime.date.today( ) next轮year = today.replace(year=today.year+1).strftime("%Y.%m.%d") print next轮year 注意是怎样递增的, replace 方法的使用直接给 date 和 time 实例的属性赋值 一想法颇诱惑力,但实些实例是无法改写的实是好,意味着们 将些实例用作合的员,或者的键,所,新实例能通过创建获得,而 无法通过修改一个的实例来达 datetime 模块通过 timedelta 类型时间差个时间实例的差值把它想象一 段时间供了一些本支持个类型允许你使用给定的时间,在一个指定的 期增减时间,时个类型作 time 和 date 实例的差值计算结果 >>> import datetime >>> NewYearsDay = datetime.date(2005, 01, 01) >>> NewYearsEve = datetime.date(2004, 12, 31) >>> oneday = NewYearsDay - NewYearsEve >>> print oneday 1 day, 0:00:00 >>> timedelta 实例在内部被表示数,数和微数,但通过直接供些参数 或者他参数,如钟数小时数和周数来构建一个 timedelta 实例他类型的差值, 如的,的,则故意没供,因它们的含和操作结果等,在一些争 但第方 dateutil 包则供了些特性, https://moin.conectiva.com.br/DateUtil datetime 的计很,时很谨慎策略是,实难预测的任务那些 在的系统中需要用到实的任务,强求做到无所能前的实,经 供了良好的接口,并用于大多数情况,更要的是,供了一个坚实的础 便第方模块在础继续发展 一个反 datetime 计谨慎的地方是模块对时的支持虽然 datetime 供了很好 时间和务计算 107 的查询和置时信的方法,但如果没一个外部的来源来供 tzinfo 类型的非抽象 化的子类,那些方法会什用少个第方包 datetime 供了时支持 前面经到过的 dateutil pyTZ, http://sourceforge.net/projects/pytz/ 十制 decimal 是 Python 标准供的模块,是 Python 2.4 新引入的内容,并终给 Python 带来了十制数学计算感谢 decimal,们在终于拥了十制数数据类型, 界精度和浮点们来仔细看看个方面 十制数数据类型 数是被储制,而是一个十制数的序列 界精度 用于储数的数的数目是固定的对于每个十制数对象而言,那是一个固定 的参数,但是的十制数对象被置使用数目的数 浮点 十制小数点并没一个固定的位置或者样讲所数的总数是固定的, 小数点面的数并没一个固定的数目如果数目是固定的,那做固点 数质而非浮点数质数据类型 样的数据类型很多用途大用途就是用于务计算,特别是,decimal.Decimal 标准的制 float 供了更多的高功能大的优点是,所用户输入的数 就是所的限位数的数,都能够被精确地表示出来作反例的是,用 制浮点储用户输入的数据,常常能够格按照原样表示出来 >>> import decimal >>> 1.1 1.1000000000000001 >>> 2.3 2.2999999999999998 >>> decimal.Decimal("1.1") Decimal("1.1") >>> decimal.Decimal("2.3") Decimal("2.3") 种完全一的表示用于计算而于制浮点,举个例子 >>> 0.1 + 0.1 + 0.1 - 0.3 5.5511151231257827e-17 虽然差异极小,接于 0,但是种微小差异利于们行靠的相等较算 外,些微小的差异值会断累于个原因,对于那些目计算,对 相等较计算要求很格的程序,decimal 制浮点更加用 108 第 3 >>> d1 = decimal.Decimal("0.1") >>> d3 = decimal.Decimal("0.3") >>> d1 + d1 + d1 - d3 Decimal("0.0") 们通过整数符串或者元构建 decimal.Decimal要一个浮点数创建一个 decimal.Decimal,首先需要将浮点转化符串是必要的一,们在显 式地指明转化的细节,甚包括了对错误的表示十制数包括了一些特殊的值, 如 NaN表了非数负无大,-0一创建功,decimal.Decimal 对象是无法修改的,就像 Python 中的他数一样 decimal 模块本是实了们在学校学到的一些数学算法则对于指定的精度 要求,它总是能给出没截断的结果 >>> 0.9 / 10 0.089999999999999997 >>> decimal.Decimal("0.9") / decimal.Decimal(10) Decimal("0.09") 结果中的数的数目超过了精度要求,则根据前的舍入方式对结果行舍入处理 几种舍入方式但默认的是 round-half-even 方式译者注四舍入,如果是 5,则 舍入到接的偶数举个例子2.45 保留一位小数,结果是 2.4 decimal 模块引入了效位的概念,例如 1.30+1.20 结果是 2.50的 0 是了指出 效位对于到务计算的用程序,是经常采用的表示方法对于乘法,教 书式的方法会使用乘数中的所数 >>> decimal.Decimal("1.3") * decimal.Decimal("1.2") Decimal("1.56") >>> decimal.Decimal("1.30") * decimal.Decimal("1.20") Decimal("1.5600") 和他内建数类型一样,如 float 和 int,decimal 对象拥数的一些标准属性,除 之外它一些特殊的方法体信参看文档中对方法的绍相的 示例 decimal 数据类型通常都作在一个体的境中,就是说一些配置信经被预先 定了每个线程都它自的前境拥独立的文境意味着每个线程都 做自的改和定而互相影响通过 decimal 模块供的 getcontext 和 setcontext 函数,们和修改前线程的境 像于硬的制浮点数,decimal 模块的精度由用户置默认是 28 位 对于任意一个给定的题,它都被置得足够大 >>> decimal.getcontext( ).prec = 6 # 精度被设为 6... >>> decimal.Decimal(1) / decimal.Decimal(7) 时间和务计算 109 Decimal("0.142857") >>> decimal.getcontext( ).prec = 60 # ...被设为 60 个数字 >>> decimal.Decimal(1) / decimal.Decimal(7) Decimal("0.142857142857142857142857142857142857142857142857142857142857") 但 decimal 中一些是那简单和本的东西本,decimal 实了标准的数 学计算,在 http://www2.hursley.ibm.com/decimal/查到更多细节意味着 decimal 支持信的概念信表示在计算中出的一些常的情况如,1/0,0/0,无 大/无大根据各个用程序的需求,的信被忽略掉了,的被用来供 额外信,的则被作异常对于每个信,都一个标志和一个陷制器 一个信发生了,它的标志由 0 开始递增,然如果陷制器被置 1,它会抛 出一个异常给了程序员极大的力和自由度来配置 decimal,满足他们特定的 需求 decimal 的优点如的多,什人坚持要用 float?什 Python像很多他流 传广泛的语言一样,但 Cobol 和 Rexx 是很容易想到的个例外会一开始就采用浮 点制数作它默认的是唯一的非整数数据类型?然,理由给出一大 堆,但终都结到一点,度!看看面的实验 $ python -mtimeit -s'from decimal import Decimal as D' 'D("1.2")+D("3.4")' 10000 loops, best of 3: 191 usec per loop $ python -mtimeit -s'from decimal import Decimal as D' '1.2+3.4' 1000000 loops, best of 3: 0.339 usec per loop 对面的输出做个简单的翻译在机器一老的 1.2 GHz Athlon PC,行 Linux,Python 每执行 300 万 float 加法使用个人计算机供的支持数学计 算的硬,但能执行 5000 Decimal 的加法完全由完 本,如果需要对非整数做万的加法,坚持用 float在过去,一普通 计算机的算度千倍地落于在的计算机的度实那是久之前,因 ,程序行在廉的机器时,使是做少量的计算会多限制而 Rexx 和 Cobol 生于大型机系统,些系统没廉的计算机算度快,格 是些计算机的千万倍类大型机系统的买许负得种方便而 易用的十制数学计算,但大多数他语言,通常生于更加廉的性能限的机 器之,无法负种开销 幸的是,需要对非整数行大量数学计算的用程序的数量相对较少,在 的普通计算机,量的十制数学计算会引一些的性能题因, 大多数用程序都利用 decimal 在各方面的优势,包括那些需要继续行在 Python 2.3 的程序本 2.4 之,decimal 经了 Python 标准的一部 要了解更多在 Python 2.3 中 decimal 的细节,参看 http://www.taniquetil.com.ar/ facundo/bdvfiles/get轮decimal.html 110 第 3 3.1 计算昨和明的期 感谢:Andrea Cavalcanti 任务 你想获得的期,并计算昨和明的期 解方案 无论何时你遇到时间化或者时间差的题,先考虑 timedelta import datetime today = datetime.date.today( ) yesterday = today - datetime.timedelta(days=1) tomorrow = today + datetime.timedelta(days=1) print yesterday, today, tomorrow #输出2004-11-17 2004-11-18 2004-11-19 论 自 datetime 模块出来,个题在 Python 邮列表中频频露面首碰到 个题时,人们的第一个想法是写样的码yesterday = today – 1,但结果是一个 TypeError: unsupported operand type(s) for -: ‘datetime.date’ and ‘int’ 一些人认是个 bug,并暗示 Python 能够猜测出他们的意然而,Python 的 一个指原则是在模糊含混面前拒猜测,是 Python 简洁和强大的原因猜 测意味着需要用启发的方式将 datetime 割裂开来,需要猜测你想减去的是 1 是 1 ,再或者脆是 1 ? Python 如它一贯的方式,并尝试猜测你的意,而是期待你明确指定你自的意 如果想减去长度 1 的一个时间差,明确地编写相码再或者,想加 长度 1 的时间差值,使用 timedelta 配合 datetime.datetime 对象,样用 样的语法编写相操作许对每个任务都想使用种方法,因种方法给了你 足够的自由度,时保持着简洁直考虑面的段 >>> anniversary = today + datetime.timedelta(days=365) # 增加 1 年 >>> print anniversary 2005-11-18 >>> t = datetime.datetime.today( ) # 获得现在的时间 >>> t datetime.datetime(2004, 11, 19, 10, 12, 43, 801000) >>> t2 = t + datetime.timedelta(seconds=1) # 增加 1 秒 >>> t2 datetime.datetime(2004, 11, 19, 10, 12, 44, 801000) 时间和务计算 111 >>> t3 = t + datetime.timedelta(seconds=3600) # 增加 1 小时 >>> t3 datetime.datetime(2004, 11, 19, 11, 12, 43, 801000) 如果你想在期和时间的计算点新花样,使用第方包,如 dateutil和 内建的 datetime 协作和经的 mx.DateTime举个例子 from dateutil import relativedelta nextweek = today + relativedelta.relativedelta(weeks=1) print nextweek #输出2004-11-25 然而,总是它能简单地作了保持简单,本节解方案中使用了 datetime.timedelta 更多资料 https://moin.conectiva.com.br/DateUtil?action=highlight&value= DateUtil 的 dateutil 文 档,Library Reference 中于 datetime 的文档mx.DateTime 的资料在 http://www.egenix. com/files/python/mxDateTime.html 找到 3.2 找一个期五 感谢:Kent Johnson、Danny Yoo、Jonathan Gennick、Michael Wener 任务 你想知道一个期五的期包括,如果是期五并特定格式将打 印出来 解方案 通过 Python 标准的 datetime 模块,任务轻松完 import datetime, calendar lastFriday = datetime.date.today( ) oneday = datetime.timedelta(days=1) while lastFriday.weekday( ) != calendar.FRIDAY: lastFriday -= oneday print lastFriday.strftime('%A, %d-%b-%Y') # 输出Friday, 10-Dec-2004 论 面的码段帮助们找到一个期五的期,并确的格式打印,无论 个期五是否在一个,甚在一没系在个例子中,们试 112 第 3 找期五包括,如果是期五期五的整数表示是 4,但们避免 直接依赖和使用个数,们入 Python 标准的 calendar 模块,并利用它的 calendar.FRIDAY 属性实个属性值就是 4首先们创建一个做 lastFriday 的 量,并将它定的期,然断地向前对检查,直到找到某个期,它 的 weekday 的值是 4 一找到了需要的期,使用 datetime.date 类的 strftime 方法,很容易地将转化 们需要的格式 一个方法,看去更简洁一点,利用内建的 datetime.date.resolution,而是 显式地创建一个 datetime.timedelta 实例来表示一的时间长度 import datetime, calendar lastFriday = datetime.date.today( ) while lastFriday.weekday( ) != calendar.FRIDAY: lastFriday -= datetime.date.resolution print lastFriday.strftime('%d-%b-%Y') datetime.date.resolution 个类属性的值和第一段码中的 oneday 量的值完全一样 过,resolution 能会你犯错datetime 模块的类的 resolution 属性的值 质对于 date 类,个值是 timedelta(days=1),但对于 time 和 datetime 类,值 timedelta(microseconds=1)将它们混合搭配如,给 datetime.datetime 实例加 一个 datetime.date.resolution,但时很容易混淆并误用本节解方案中用了一个 命的 onday 量,更加通用更加明确,没任何能引误解的地方因, 种方式更加 Python 风格是什它被做式的解方案的原因 一点改,循都省略,所用在循中每减去一并做较 借助模算,一计算出需要减去的数 import datetime, calendar today = datetime.date.today( ) targetDay = calendar.FRIDAY thisDay = today.weekday( ) deltaToTarget = (thisDay - targetDay) % 7 lastFriday = today - datetime.timedelta(days=deltaToTarget) print lastFriday.strftime('%d-%b-%Y') 如果对段码如何作疑,许需要复一模算的知识,参看 http://www.cut-the-knot.org/blue/Modulo.shtml 但使用你认清楚明的方法,而用心性能题记得 Hoare 的言吗 总是被错误的 Knuth 的言论,但实他是引用 Hoare过早优化是万恶之 源们看看什在的优化早 减去共的计算部计算前期,格式化并打印,在 一 4 岁高龄的行着 Linux 和 Python 2.4 的老个人计算机,慢的方法就是被用作解方案的方法,因 时间和务计算 113 它足够清晰直耗时 18.4μs快的方法避免循,并使用各种技使之 耗时 10.1μs 真的需要很频繁地行段码吗?于那 8μs 的差异都得很要如果用前较新 的硬,个差异值会得更小?如果要考虑计算的期和格式化结果的开 销,需要再加 37μs,包括了 print 语的 I/O 开销样,慢的是清晰的方法将 耗时 55μs,而快的,是精炼的方式,耗时 47μs,点差异实在是值得心 更多资料 Library Reference 中 datetime 模块和 strftime 的文档 http://www.python.org/doc/lib/ module-datetime.html 和 http://www.python.org/doc/current/lib/node208.html 3.3 计算期之间的时段 感谢:Andrea Cavalcanti 任务 给定个期,需要计算个期之间隔了几周 解方案 标准的 datetime 和第方的 dateutil 模块准确地说是 dateutil 的 rrule.count 方法很易 于使用在入了确的模块之,任务得非常简单 from dateutil import rrule import datetime def weeks轮between(start轮date, end轮date): weeks = rrule.rrule(rrule.WEEKLY, dtstart=start轮date, until=end轮date) return weeks.count( ) 论 函数 weeks轮between 用一个开始期和一个结束期作参数,并实例化一个规则用于 计算者之间的周数,返回规则的 count 方法的结果质写码述个过程 简单多了个方法返回一个整数它能是所谓的半周举个例子,8 被认是 2 周面做个测试 if 轮 轮name轮 轮=='轮 轮main轮 轮': starts = [datetime.date(2005, 01, 04), datetime.date(2005, 01, 03)] end = datetime.date(2005, 01, 10) for s in starts: days = rrule.rrule(rrule.DAILY, dtstart=s, until=end).count( ) print "%d days shows as %d weeks "% (days, weeks轮between(s, end)) 114 第 3 测试结果是 7 days shows as 1 weeks 8 days shows as 2 weeks 如果喜,就没必要给递规则指定,而是修改函数体,如面样,用 一条语完所操作 return rrule.rrule(rrule.WEEKLY,dtstart=start轮date,until=end轮date).count() 样作得很好但坦率地说,是倾向于给递规则指定一个,然会 得惯然码经很好用了,许是的个人偏好 更多资料 参考 dateutil 模块的文档, https://moin.conectiva.com.br/DateUtil?action=highlight&value= DateUtil, Library Reference 中 datetime 的文档 3.4 计算歌曲的总播时间 感谢:Anna Martelli Ravenscroft 任务 你想获一个列表中的所歌曲的播时间之和 解方案 们使用 datetime 标准模块和内建的 sum 函数来完个任务 import datetime def totaltimer(times): td = datetime.timedelta(0) # 将总和初始化必须是 timedelta duration = sum([ datetime.timedelta(minutes=m, seconds=s) for m, s in times], td) return duration if 轮 轮name轮 轮== '轮 轮main轮 轮': # 测试模块是否作为主脚本运行 times1 = [(2, 36), # 列出包含的元组分,秒 (3, 35), (3, 45),] times2 = [(3, 0), (5, 13), (4, 12), (1, 10),] assert totaltimer(times1) == datetime.timedelta(0, 596) assert totaltimer(times2) == datetime.timedelta(0, 815) 时间和务计算 115 print ("Tests passed.\n" "First test total: %s\n" "Second test total: %s" % ( totaltimer(times1), totaltimer(times2))) 论 在作之余,喜听歌,很长的一个歌曲列表希望能够中挑选一些歌曲 并获得它们的总播时间,而无先建立一个新的播列表写了一个脚本来完 个任务 计算个 datetime 对象之间的差异时,一般返回一个 datetime.timedelta 对象,创 建你自的 timedelta 实例来表任何给定的时长datetime 模块中他的类,如 datetime 类,是表一个时间点,们需要计算的是总时长,所很明显, 们需要使用 timedelta datetime.timedelta 很多的选参数数数微数毫数钟数 小时数和周数因,要创建一个实例,需要明确地传递一个参数避免混淆如果 简单地调用 datetime.timedelta(m, n),而指明参数,个类会通过位置来确定参数, 把 m 和 n 做数和数,而产生奇怪的结果个错误经郁闷过一段时 间,所一定要做好测试作 要对一个对象列表,如 timedelta 列表,使用内建的 sum 函数,必给 sum 传入第 个参数作初始化的值质否则,默认的初始值是 0,整数 0你试给它加一个 timedelta 对象时,你会得到一个错误外,传入的第一个参数是一个迭体, 中的所对象都支持数加法特别指出,符串支持数加法,在强 烈建要使用 sum 把很多的列表接来在 Python 2.4 中,们将方括[] 换圆括(),而使用生器表达式而是列表来作 sum 的第一个参数, 需要处理几千首歌的时候,样做会更方便 于测试用例,手创建了一个元的列表,每个元素记录了歌曲的时长,体的 数据是钟数和数脚本再一改加强,如增强对格式的辨识 能力如类似 mm:ss 的格式,甚增加直接文中读信或者直接音乐的 能力 更多资料 Library Reference 中 sum 和 datetime 部 3.5 计算期之间的作 感谢:Anna Martelli Ravenscroft 116 第 3 任务 你想计算个期之间的作,而非数 解方案 由于作和节在的家,地,甚在一家部门之间,都会 所,所并在一个内建的简单办法来解个题过,利用 dateutil, 配合 datetime 对象,完个任务是很难 from dateutil import rrule import datetime def workdays(start, end, holidays=0, days轮off=None): if days轮off is None: days轮off = 5, 6 # 默认周六和周日 workdays = [x for x in range(7) if x not in days轮off] days = rrule.rrule(rrule.DAILY, dtstart=start, until=end, byweekday=workdays) return days.count( ) - holidays if 轮 轮name轮 轮 == '轮 轮main轮 轮': # 主脚本运行时的测试代码 testdates=[(datetime.date(2004,9,1),datetime.date(2004,11,14),2), (datetime.date(2003,2,28),datetime.date(2003,3,3),1),] def test(testdates, days轮off=None): for s, e, h in testdates: print'total workdays from %s to %s is %s with %s holidays'%( s, e, workdays(s, e, h, days轮off), h) test(testdates) test(testdates, days轮off=[6]) 论 是的第一个 Python 目给定一个开始期和一个结束期含,需要 者们确地计算出数在 Python 2.2 中个题能会点麻烦而在,在 datetime 模块和第方包的 dateutil 的帮助,个题简单多了 函数 workdays 给量 day轮off 赋了一个合的默认值除非明确地传入了一个参数作 dayoff 的值,个值实是一个非作的序列在的,的人 的非作,但非作数量通常都是少于作的,所记录和修改非作总 的来说要更容易一些把非作做一个参数,是了在遇到需求时给 days轮off 传递的值接着,个函数使用列表来创建一个实的作列表, 就是那些在非作列表 days轮off 中的子之,函数就行实计算了 本节例子中的要量做 days,它是 dateutil 的 rrule递规则类的一个实例 们给 rrule 类传入的参数创建的规则对象在本例中,传递了一个常 时间和务计算 117 用的rrule.DAILY的规则,指定了始期和结束期质它们必都是 datetime.date 对象,时指定了需要统计在内的作weekdays,根据个规则, 需要调用 days.count 方法来计算符合条的情况发生了多少第 3.3 节中 rrule 的 count 方法的他用 很容易地定自的周给 days轮off 传递任何需要的值在本节中,默认值 是标准的美周时间,周和周然而,如果你的需要作 4 ,如, 周到周五,传递参数使得 days轮off=(5, 6, 0)使如第个测试所示,容器中实 一,要确保传递给 days轮off 的值是迭对象,如列表或者元 做一点简单但用的加强,如检查你的开始期和结束期是否是周,并 用一个 if/else 语来处理周班,时地修改 days轮off然更多加强 的地方,如加入病休处理,行自动的节查询处理,因而无直接传入节的数 目本节的做法于个目的,第 3.6 节实了一个节列表 更多资料 参考 dateutil 文档, https://moin.conectiva.com.br/DateUtil?action=highlight&value= DateUtil, Library Reference 中的 datetime 部第 3.3 节中 rrule.count 的他用法第 3.5 节中的 自动节查询 3.6 自动查询节 感谢:Anna Martelli Ravenscroft、Alex Martelli 任务 的家,的地,甚一个的会,节都能所需要找 到一个办法,能够在给定始期和结束期的条,自动获总共的节假数 解方案 给定个期,它们之间的节的期能是固定的,如复活节和劳节美 于复活节的节,如节礼固定期的节,如圣节或者你们 的节如 CEO 的生过你都利用 datetime 和第方模块 dateutil 处 理所些节 一个灵活的结构,能够出多的能性,将别包装函数,并在的 时候调用那些函数 import datetime from dateutil import rrule, easter try: set 118 第 3 except NameError: from sets import Set as set def all轮easter(start, end): # 返回在开始和结束日期之间的复活节列表 easters = [easter.easter(y) for y in xrange(start.year, end.year+1)] return [d for d in easters if start<=d<=end] def all轮boxing(start, end): #返回在开始和结束日期之间的节礼日列表 one轮day = datetime.timedelta(days=1) boxings = [easter.easter(y)+one轮day for y in xrange(start.year, end.year+1)] return [d for d in boxings if start<=d<=end] def all轮christmas(start, end): #返回在开始和结束日期之间的圣诞节列表 christmases = [datetime.date(y, 12, 25) for y in xrange(start.year, end.year+1)] return [d for d in christmases if start<=d<=end] def all轮labor(start, end): #返回在开始和结束日期之间的劳动节列表 labors = rrule.rrule(rrule.YEARLY, bymonth=9, byweekday=rrule.MO(1), dtstart=start, until=end) return [d.date( ) for d in labors] # 无须测试是否在两个日期之间 def read轮holidays(start, end, holidays轮file='holidays.txt'): # 返回在开始和结束日期之间的假期列表 try: holidays轮file = open(holidays轮file) except IOError, err: print 'cannot read holidays (%r):' % (holidays轮file,), err return [ ] holidays = [ ] for line in holidays轮file: # 跳过空行和注释 if line.isspace( ) or line.startswith('#'): continue # 试图解析格式 YYYY, M, D try: y, m, d = [int(x.strip( )) for x in line.split(',')] date = datetime.date(y, m, d) except ValueError: # 检测无效行并继续 print "Invalid line %r in holidays file %r" % ( line, holidays轮file) continue if start<=date<=end: holidays.append(date) holidays轮file.close( ) return holidays holidays轮by轮country = { # 将各个国家代码映射到一系列函数 时间和务计算 119 'US': (all轮easter, all轮christmas, all轮labor), 'IT': (all轮easter, all轮boxing, all轮christmas), } def holidays(cc, start, end, holidays轮file='holidays.txt'): # 从文件中读取可用的假期 all轮holidays = read轮holidays(start, end, holidays轮file) # 加入用函数计算出的假期 functions = holidays轮by轮country.get(cc, ( )) for function in functions: all轮holidays += function(start, end) # 消除重复 all轮holidays = list(set(all轮holidays)) # 使用下面两行可以返回一个排序后的列表 # all轮holidays.sort( ) # return all轮holidays return len(all轮holidays) # 如果想返回列表注释此行 if 轮 轮name轮 轮 == '轮 轮main轮 轮': test轮file = open('test轮holidays.txt', 'w') test轮file.write('2004, 9, 6\n') test轮file.close( ) testdates = [ (datetime.date(2004, 8, 1), datetime.date(2004, 11, 14)), (datetime.date(2003,2,28),datetime.date(2003,5,30)), (datetime.date(2004,2,28),datetime.date(2004,5,30)), ] def test(cc, testdates, expected): for (s, e), expect in zip(testdates, expected): print 'total holidays in %s from %s to %s is %d(exp %d)' % ( cc,s,e,holidays(cc,s,e,test轮file.name),expect) print test('US', testdates, (1,1,1) ) test('IT', testdates, (1,2,2) ) import os os.remove(test轮file.name) 论 经作的一家 3 个的会,根据契, 3 个会的节假 时,们得考虑如雪snow days或他类型的休假,本它们 等于节了处理所些能发生化的节假,们好将标准的节 计算抽出来,封装单个的函数,如们完了 all轮easter,all轮labor,等等 类型的计算在需要的时候调用 虽然半开间边界是包括的,但右边界并包括在 Python 中是允许的而, 在计算更灵活性,出题的能更小,本节仅仅处理间右边界都包 括而那是期间的概念所定的,dateutil 是于个概念作,所, 是一个很显然的选择 120 第 3 每个函数都负确保返回的结果满足们的需要返回在个期含之间的 datetime.date 实例的列表举个例子,如 all轮labor,们用 date 方法,强制将 dateutil 的 rrule 返回的 datetime.datetime 结果转化 datetime.date 实例 能会将某定一个节假如雪,一,并能会用一个 文本文来记录些特殊的子在本例中,read轮holidays 函数执行读文本文的任 务,每行读一个期,格式选择将个函数构更模糊 的期解析器,如第 3.7 节中展示的那样 如果每行程序都需要查询节很多,能想做一些优化作,它读和解 析一,然每在需要的时候调用些解析结果过,过早优化是万恶之源, Knuth 引用 Hoare 的说过通过避免种显然的优化,们获得了清晰和灵 活假些函数是在交互式境中调用的,而期文能在读之间被编 辑和修改如果们每都做任何假地读整个文,则完全需要检查自 读之文无做过任何修改 由于的家庆祝的节,本节例子中供了一个很本的 holidays轮by轮country 在因特网做很多调查并根据需要断地充实个很要的一点是, 个允许调用的节函数,根据传递给 holidays 函数的的家编码来确 定如果你的很多会,很容易地创建一个于会的,传递会 编码而非家编码给 holidaysholidays 函数再去调用合的函数包括了无条调用 的 read轮holidays 函数,接所的结果,清除复内容,然返回列表的长度如果 你乐意,直接返回列表,需简单地码中注释掉的行开始作 更多资料 3.7 节中的模糊解析dateutil 的文档, https://moin.conectiva.com.br/DateUtil?action= highlight&value=DateUtil,Library Reference 中的 datetime 文档 3.7 期的模糊查询 感谢:Andrea Cavalcanti 任务 你的程序需要读并接一些并符合标准的yyyy, mm, dddatetime 格式 解方案 第方 dateutil.parser 模块给出了一个简单的解答 import datetime import dateutil.parser 时间和务计算 121 def tryparse(date): # dateutil.parser 需要一个字符串参数根据一些惯例,我们 # 可以从 4 种“date”参数创建一个 kwargs = { } # 假设没有命名参数 if isinstance(date, (tuple, list)): date = ''.join([str(x) for x in date]) # 拼接序列 elif isinstance(date, int): date = str(date) # 将整数变为字符串 elif isinstance(date, dict): kwargs = date # 接受命名参数字典 date = kwargs.pop('date') # 带有一个 'date'字符串 try: try: parsedate = dateutil.parser.parse(date, **kwargs) print 'Sharp %r -> %s' % (date, parsedate) except ValueError: parsedate=dateutil.parser.parse(date,fuzzy=True,**kwargs) print 'Fuzzy %r -> %s' % (date, parsedate) except Exception, err: print 'Try as I may, I cannot parse %r (%s)' % (date, err) if 轮 轮name轮 轮 == "轮 轮main轮 轮": tests = ( "January 3, 2003", # 字符串 (5, "Oct", 55), # 元组 "Thursday, November 18", # 没有年的长字符串 "7/24/04", # 带斜线的字符串 "24-7-2004", # 欧式的字符串格式 {'date':"5-10-1955","dayfirst":True}, # 包括了 kwarg 的字典 "5-10-1955", # 日在前,无 kwarg 19950317, # 非字符串 "11AM on the 11th day of 11th month,in the year of our Lord 1945", ) for test in tests: # 测试日期格式 tryparse(test) # 尝试解析 论 dateutil.parser 的 parse 函数用于很多数据格式本节码中展示了中的一部 个解析器处理英语的位或者四位的带一些限制如果带 参数调用 parse,它首先尝试用如的序来解析符串mm-dd-yy如果解析的结 果合逻辑,如例子中的那样,它尝试解析27-7-2004样的符串,无果 它会尝试yy-mm-dd如果传入了 dayfirst 或 yearfirst 样的键们在测 试中是样做的,parse 会试根据键行解析 本节的测试码定了解析器能会碰到的一些边界测试用例,如通过一个元, 一个整数ISO 格式,无空格,甚一个短语来传入期了测试键参数,本 122 第 3 节的 tryparse 函数接一个作参数,函数找到date键对的值作解析 的对象,并将余部作键参数传递给 dateutil 的解析器 dateutil 的解析器能够供一定程度的模糊解析,要你能够给它一点示便确 定各部的处理方式,如小时测试中所用的短语包含了 AM在式的编写码 作中,避免依赖模糊解析,或者做一些预处理作,或者少供某种机制来 检查需要解析的期的准确性 更多资料 更多的期解析算法的资料,参看 dateutil 的文档 https://moin.conectiva.com.br/ DateUtil?action=highlight&value=DateUtil而于期处理部,参看 Library Reference 中 datetime 的文档 3.8 检查夏时是否在实行 感谢:Doug Fort 任务 你想了解前时的夏时是否在执行 解方案 你第一个想法能是去检查 time.daylight,但很遗憾,那行样做 import time def is轮dst( ): return bool(time.localtime( ).tm轮isdst) 论 在的时和大多数他时一样,time.daylight 永是 1,因 time.daylight 的含 是,时是否在一中的某些时候会执行夏时制Daylight Saving Time, DST, 和前是否在实行夏时制无 DST 在实行的时候,调用 time.localtime 获得元的一的值才是 1, 否则会是 0质好就是们需要检查的本节将个检查操作封装一个函数, 调用了内建的 bool 类型来确保返回结果是一个优的 true 或者 false,而是一个粗 糙的 1 或者 0质部改良是必的,但认效果错直接相 的,如 time.localtime()[-1],但是通过 tm轮isdst 属性来获信显然读性会 更好 时间和务计算 123 更多资料 Library Reference 和 Python in a Nutshell 中于 time 模块的内容 3.9 时转换 感谢:Gustavo Niemeyer 任务 假你在身处西班牙,你想将发生在中的某个的时间转换西班牙时间 解方案 第方包 dateutil 中对 datetime 供了时支持面给出一个置本地时的方法, 并打印出前时间来检查结果的确性 from dateutil import tz import datetime posixstr = "CET-1CEST-2,M3.5.0/02:00,M10.5.0/03:00" spaintz = tz.tzstr(posixstr) print datetime.datetime.now(spaintz).ctime( ) 时之间的转换仅是能的,而对于们来说是必要的举个例子, 们看看如果根据西班牙时间,一届奥会什时候开始 chinatz = tz.tzoffset("China", 60*60*8) olympicgames = datetime.datetime(2008, 8, 8, 20, 0, tzinfo=chinatz) print olympicgames.astimezone(spaintz) 论 posixstr 的符串是西班牙时的 POSIX 风格的表示法个符串供了标准 时和夏时的CST 和 CEST,它们的偏移量UTC+1 和 UTC+2, DST 开 始和结束的确时间一个期的凌晨 2 点,十一个期的 凌晨 3 点们检查一 DST 时的边界确保确性 assert spaintz.tzname(datetime.datetime(2004, 03, 28, 1, 59)) == "CET" assert spaintz.tzname(datetime.datetime(2004, 03, 28, 2, 00)) == "CEST" assert spaintz.tzname(datetime.datetime(2004, 10, 31, 1, 59)) == "CEST" assert spaintz.tzname(datetime.datetime(2004, 10, 31, 2, 00)) == "CET" 所的些 asserts 都能利地通过测试,而确保时在些时间之间的 换是立的 注意返回到标准时的时间是凌晨 3 点,而实的换时间点被记凌晨 2 点中 间一个小时的差异,凌晨 2 点和凌晨 3 点,能保持一种情况发生了 124 第 3 一是在 CEST 时,一是 CET 时目前,通过标准的 Python 期和时间支 持来无歧地表示个时刻能就是什会荐你储无歧的 UTC 的 datetime 实例,并了显示的目的而行时转换 了将中时转换西班牙时,们使用了 tzoffset 来记录个实,中 UTC 时间前了 8 个小时tzoffset 总是用来和 UTC 较,而是和某个特定的时 较注意们是如何根据时信创建出 datetime 实例的对于个时之间 的转换,使给出的时间是于本地时的,一总是很必要的如果你根据 时信创建实例,你会得到一个 ValueError: astimezone( ) cannot be applied to a naive datetime在没时信的情况,创建的 datetime 实例会被简化处理质完全忽略 掉时信于个目的,dateutil 供了 tzlocal 类型,创建于前的本地时 的实例 除了前面到的类型,dateutil 供了 tzutc,创建于 UTC 的实例tzfile,使 用标准的制时文tzical,创建于 iCalendar 时的实例然更多他 类型,就细绍了 更多资料 dateutil 模块的文档, https://moin.conectiva.com.br/DateUtil?action=highlight&value= DateUtil, Library Reference 中 datetime 的文档 3.10 反复执行某个命 感谢:Philip Nunez 任务 需要反复地需要的时间间隔执行某个命 解方案 time.sleep 函数供了一个简单的解办法 import time, os, sys def main(cmd, inc=60): while True: os.system(cmd) time.sleep(inc) if 轮 轮name轮 轮 == '轮 轮main轮 轮' : numargs = len(sys.argv) - 1 if numargs < 1 or numargs > 2: print "usage: " + sys.argv[0] + " command [seconds轮delay]" sys.exit(1) 时间和务计算 125 cmd = sys.argv[1] if numargs < 3: main(cmd) else: inc = int(sys.argv[2]) main(cmd, inc) 论 用本节中的方法来周期性地行某命,完某种检查如询,或者执行 断复的操作,如浏器加载某个内容断发生化的 URL,样确保目 前的浏结果是较新的本节码创建了一个做 main 的函数,一个由 if 轮 轮name轮 轮=='轮 轮main轮 轮':限定的体部,部在脚本作脚本行时才会被执行 体部检查命行的参数,并调用 main 函数或者输出使用方法的信,如果给的参 数数目对的是很好的脚本编写结构,时使得它的功能被他脚本通 过模块入的方式调用 main 函数接一个做 cmd 的符串参数,那是你想传递给操作系统 shell 用于执行 的命,一个选的时间周期,默认是 60 1 钟main 函数体处于永久 的循之中,它或者用 os.system 来执行命,或者通过 time.sleep 来等待无耗 资源 脚本的体会检查你传递给脚本的命行参数,它通过 sys.argv 来获那些参数 第一个参数,sys.argv[0],是脚本的,脚本需要确定自的身份并打印信时 是很用的参考脚本的体检查一到个参数第一个强制性的是需要行的 命你能需要用引将命括来,是了 shell 解析命时,将行命 的参数和脚本的参数混淆来要的是,加了引之,无论面的内容是什 样的,它都是一个单一的参数第个选的参数是行之间的间隔数如 果忽略第个参数,体部会用默认的间隔时间60s周期来调用传入的命 注意,如果第个参数,体部会将它由符串sys.argv 中所的元素都是符 串转化整数,通过调用内建的 int 类型完一转换 inc = int(sys.argv[2]) 如果第个参数是一个无法转换整数的符串如,它是一个含数的符序 列,用面的方式调用 int 会产生一个异常,然脚本的执行会结束并打印出一些错 误信如 Python 的计哲学那样,除非明确地指定,错误悄无声地被略 过所,如果允许脚本接任何符串作第个参数,并在发生转换错误时采 默认的行动,那实是一个很好的计 如果愿使用循和 time.sleep,本一些 Python 标准模块 sched 的内容, 第 3.11 节 126 第 3 更多资料 Library Reference 和 Python in a Nutshell 中于标准模块 ostime 和 sys 的文档 3.11 节 3.11 定时执行命 感谢:Peter Cogolo 任务 需要在某个确定的时刻执行某个命 解方案 是标准的 sched 模块所针对的任务 import time, os, sys, sched schedule = sched.scheduler(time.time, time.sleep) def perform轮command(cmd, inc): schedule.enter(inc, 0, perform轮command, (cmd, inc)) # re-scheduler os.system(cmd) def main(cmd, inc=60): schedule.enter(0, 0, perform轮command, (cmd, inc)) # 0==right now schedule.run( ) if 轮 轮name轮 轮 == '轮 轮main轮 轮' : numargs = len(sys.argv) - 1 if numargs < 1 or numargs > 2: print "usage: " + sys.argv[0] + " command [seconds轮delay]" sys.exit(1) cmd = sys.argv[1] if numargs < 3: main(cmd) else: inc = int(sys.argv[2]) main(cmd, inc) 论 述码供了和第 3.10 节中码类似的功能,过它没使用一个简单的询, 而是使用了标准的 sched 模块 sched 是一个简单灵活又非常强大的模块,被用来在来某个时刻执行某些特定 的任务要使用 sched,首先需要创建一个 scheduler 实例对象,码中的 schedule, 创建时要带个参数第一个参数是一个被用来调用的函数,函数确定任务的时 间质通常是 time.time,函数返回某个特定的时间点到在所经历的数第 时间和务计算 127 个参数是一个供调用的函数,用来等待一段时间质通常是 time.sleep传递 他函数,函数某种人的方式衡量时间举个例子,将 sched 用于一些模拟 程序过,人的方式预时间的衡量属于 sched 的较高的用,本节并覆盖 方面内容 一了一个 sched.scheduler 实例 s,通过调用 s.enter 来安排某的发生时间, 在的第 n 开始启动将 n 0,样会立发生,或者调用 s.enterabs,某个给定的对时间作启动时间对于种方式,都需要传递 时间相对时间或者对时间,优先如果多个被计划在一时间执行,它 们必按照优先序执行,值小的会先执行,一个供调用的函数,一个容纳前 面函数所需的参数的元个调用方式都会返回一个标识,将个标识 在什地方,如果你改了注意,轻易地个计划中的质需将 标识作参数,调用 s.cancel过又是一个较高的用法,本节并涵盖 安排了一些之,调用 s.run,它会持续行,直到计划队列空的 在本节中,们展示了怎样计划一个周期性反复执行的函数 perform轮command 每 被执行,首先都会安排在 inc 之再行它自,然才行指定的系统命 种方式安排任务,计划队列永会空,函数 perform轮command 断地被 周期性调用种自计划安排是一个很要的概念,仅仅用于和 sched 相的用, 任何时候,如果你一机会来计划一个,而没周期性的机会来反复做, 你都考虑种自计划安排方式举个例子,Tkinter 的 after 方法是种 方式作的,作种自计划安排的一个用型 使对于本节所示简单的例子,相于简单询,sched 的优势极明显在第 3.10 节中,指定的延时间是指本 cmd 的执行完之,到一 cmd 执行之前 如果 cmd 的执行需要较多的时间很能,如,某些命能需要等待网返 回数据,或者由于某些繁忙的服务器,而得等待,那些命实并是真 的周期性的执行而本节的时间延是指本开始行 cmd,到一执行 cmd 之前,因周期性是被格保障的如果某个特定命所用的时间超过了 inc ,那 schedule 会暂时落于度,但终它会慢慢来,要 cmd 的均执行时间超 过 inc sched 永会略过如果你确实需要略过某,假个 对你而言再要了,必保原先获得的标识,并调用 cancel 方法来 于本节的结构和体的一些细节的解释,参考第 3.10 节 更多资料 第 3.10 节Library Reference 和 Python in a Nutshell 中的标准模块 ostimesys 和 sched 的文档 128 第 3 3.12 十制数学计算 感谢:Anna Martelli Ravenscroft 任务 需要在 Python 2.4 中行一些简单的数学计算,但需要的是十制的结果,而是 Python 默认的 float 类型 解方案 了些简单的计算中获得预期的结果,必入 decimal 模块 >>> import decimal >>> d1 = decimal.Decimal('0.3') # 指定一个十进制对象 >>> d1/3 # 试试除法 Decimal("0.1") >>> (d1/3)*3 # 看看能否复原? Decimal("0.3") 论 Python 的新手尤是那些没使用他语言行制浮点计算经验的常常会惊 异于一些简单计算的结果举个例子 >>> f1 = .3 # 赋值为一个浮点数 >>> f1/3 # 试试除法 0.099999999999999992 >>> (f1/3)*3 # 看看能否复原? 0.29999999999999999 Python 采用制浮点数学计算作默认的计算方式是很好的理由的读一读 Python FAQFrequently Asked Questions文档, http://www.python.org/doc/faq/general. html#why-are-floating-point-calculations-so-inaccurate,或者 Python Tutorial 的录部, http://docs.python.org/tut/node15.html 很多人对于唯一的选择质制浮点,并满意,他们希望能够指定精度,能够使 用十制数学计算,便用于务计算并生预期的结果很多人需要的仅仅是 预测性一个真的数值析家,然会认制浮点的计算实是完全预 测的如果你们人的某人在读本节,跳过节继续一节,谢谢译者注 猜作者是对她的个家朋说的,他读者略过就好 新的 decimal 类型通过境文置行深入的制,使得简单地置精 度对结果的截方法过,如果你所需要的是那些简单的数学计算返回 预测的结果,那 decimal 默认的境经作得很好了 时间和务计算 129 几点记住传递符串整数元或者他 decimal 对象来创建一个新的 decimal 对象,但如果你一个浮点数 n,你想通过它来创建一个 decimal 对象,确保 传递的是 str(n),而是 ndecimal 对象和他整数长整数decimal 对象交互 如,混用于数学算,但 float 类型行些限制是强制性的十制数被引入 到 Python 中,是因它能供 float 无法供的精确度和预测性如果允许直接 float 对象构建十制数,或者允许者混用于数学计算,就完全背了计十制数 的初衷一方面,如你所预料的那样,decimal 对象被强制转化 float,long int 类型 记住,decimal 然是浮点,而非固点如果需要的是固点,参考 Tim Peters 的 FixedPoint, http://fixedpoint.sourceforge.net/外,Python 中没门的 money 数据类型,过参考 3.13 节,了解如何于 decimal 建立你自的务数据格式 一点,许是那明显少对而言,一个中间计算结果产生了输入更多 的数,保留那些多余的数,并用于一的计算,到完了所计算准 备显示或者储结果时时,再对结果行截,或者脆每一都完截 对于一点,的书的建过倾向于前者,因它少更加方便 如果你在坚持 Python 2.3,通过载和安装第方扩展来利用 decimal 模块, http://www.taniquetil.com.ar/facundo/bdvfiles/get轮decimal.html 更多资料 Python Tutorial 的录 B 中对于浮点数学算的解释, http://docs.python.org/tut/node15. htmlPython FAQ,http://www.python.org/doc/faq/general.html#why-are-floating-point- calculations-so-inaccurateTim Peter 的 FixedPoint, http://fixedpoint.sourceforge.net/ 将 decimal 用于币,参考第 3.13 节decimal 经被记录在 Python 2.4 的 Library Reference 文档中了,2.3 的使用者通过载安装使用, http://cvs.sourceforge.net/viewcvs.py/ python/python/dist/src/Lib/decimal.pydecimal PEPPython Enhancement Proposal,PEP 327, http://www.python.org/peps/pep-0327.html 3.13 将十制数用于币处理 感谢:Anna Martelli Ravenscroft,、Alex Martelli、Raymond Hettinger 解方案 们使用新的 decimal 模块,配一个修改过的 moneyfmt 函数原始本由 Raymond Hettinger 编写,那是 Python 标准中 decimal 的文档的部 import decimal """ 计算意大利支票税 """ 130 第 3 def italformat(value, places=2, curr='EUR', sep='.', dp=',', pos='', neg='-', overall=10): """ 将十进制 value 转化为财务格式的字符串 places: 十进制小数点后面的数字的位数 curr: 可选的货币符号可能为空 sep: 可选的分组三个一组分隔符逗号、句号或空白 dp: 小数点指示符逗号或句号当 places 是 0 时 小数点被指定为空白 pos: 正数的可选的符号“ +”,空格或空白 neg: 正数的可选的符号“ -”、“(”,空格或空白 overall: 最终结果的可选的总长度,若长度不够,左边货币符号 和数字之间会被填充符占据 """ q = decimal.Decimal((0, (1,), -places)) # 2 places --> '0.01' sign, digits, exp = value.quantize(q).as轮tuple( ) result = [ ] digits = map(str, digits) append, next = result.append, digits.pop for i in range(places): if digits: append(next( )) else: append('0') append(dp) i = 0 while digits: append(next( )) i += 1 if i == 3 and digits: i = 0 append(sep) while len(result) < overall: append(' ') append(curr) if sign: append(neg) else: append(pos) result.reverse( ) return ''.join(result) # 获得计算用的小计 def getsubtotal(subtin=None): if subtin == None: subtin = input("Enter the subtotal: ") subtotal = decimal.Decimal(str(subtin)) print "\n subtotal: ", italformat(subtotal) return subtotal # 指定意大利税法函数 def cnpcalc(subtotal): contrib = subtotal * decimal.Decimal('.02') print "+ contributo integrativo 2%: ", italformat(contrib, curr='') return contrib 时间和务计算 131 def vatcalc(subtotal, cnp): vat = (subtotal+cnp) * decimal.Decimal('.20') print "+ IVA 20%: ", italformat(vat, curr='') return vat def ritacalc(subtotal): rit = subtotal * decimal.Decimal('.20') print "-Ritenuta d'acconto 20%: ", italformat(rit, curr='') return rit def dototal(subtotal, cnp, iva=0, rit=0): totl = (subtotal+cnp+iva)-rit print " TOTALE: ", italformat(totl) return totl # 最终计算报告 def invoicer(subtotal=None, context=None): if context is None: decimal.getcontext( ).rounding="ROUND轮HALF轮UP" # 欧洲截断规则 else: decimal.setcontext(context) # 设置上下文环境 subtot = getsubtotal(subtotal) contrib = cnpcalc(subtot) dototal(subtot, contrib, vatcalc(subtot, contrib), ritacalc(subtot)) if 轮 轮name轮 轮=='轮 轮main轮 轮': print "Welcome to the invoice calculator" tests = [100, 1000.00, "10000", 555.55] print "Euro context" for test in tests: invoicer(test) print "default context" for test in tests: invoicer(test, context=decimal.DefaultContext) 论 意大利的税务计算颇点繁琐,本节展示的情况要复多了本节供的码仅仅 用于意大利使用发票的用户早就厌倦手处理发票了,所写了个简单的脚 本来完计算终,在构之码了在样,它使用了新的 decimal 模块, 并遵循一个务计算的原则,那就是永使用制浮点 怎才能量地利用 decimal 模块务计算服务呢?十制的数学算非常直接简 单,虽然显示结果时所用的选没那清楚明本节的 italformat 函数于 Raymond Hettinger 的 moneyfmt 函数,你在 Python 2.4 的 Library Reference 的 decimal 模块节 找到了更好地生,码一些小小的修改要的化是 overall 参数 个参数使得函数创建一个由 overall 指定数位数的 decimal,并在币符如果 的和数之间填充空符将助于生标准的预期长度的结果 注意,把 subtotal = decimal.Decimal(str(subtin))中的 subtin 强制转化一个符串 将允许 getsubtotal 函数接浮点数然包括整数和符串参数质而无 132 第 3 心浮点数能会引发异常如果你的程序能处理元,构码那 种需求对来说,浮点是 getsubtotal 能遇到的参数类型,无心元 然,如果想显示美元币符,并使用的截断规则,很容易地修改码符 合你的需求举个例子,了显示美元符,需将 curr,sep 和 dp 的默认值修改 def USformat(value, places=2, curr='$', sep=',', dp='.', pos='', neg='-', overall=10): ... 如果经常需要处理很多的币,构函数,使得它在中找一 些合的参数,或者能够用他方法给它传递确的参数在理论,使用 Python 标 准中的 locale 模块是好的用于确定使用者喜好于务信的方法,但实 得使用 locale 并能给帮什忙,过在,很希望你能够独立实 个额外的任务,作检验学果的一个 的家通常的截断规则decimal 使用 ROUND轮HALF轮EVEN 作默认的截 断方式然而,欧洲的规矩是 ROUND轮HALF轮UP了使用的截断规则,如 码所示,需修改文境终结果能会大的化,但是必要 清楚,修改截断规则能影响很小,但是能忽略的确会引差异 更深入地修改文境,创建并置自的 context 类实例无论你哪 种方式影响境,通过简单的 getcontext 修改属性,或者通过 setcontext(mycontext)传入 一个定制的 context 类实例,些修改对于活动的线程都会时生效,直到你再修改 文境如果你考虑要在式的作码或者了你自的家务管理目的中 使用 decimal,确保根据你的家的务统计惯例使用确的文境,确 的截断规则 更多资料 参考 Python 2.4 的 Library Reference 于 decimal 的文档,尤是 decimal.context 部 3.14 用 Python 实的简单加法器 感谢Brett Cannon 任务 你想用 Python 制作一个简单的加法器,使用精确的十制数而是制浮点数, 并整洁的纵列显示计算结果 解方案 了执行计算,必使用 decimal 模块们的程序接输入行,每行由一个数紧跟 时间和务计算 133 一个数学算符,空行表示收到了计算前结果的求,输入 q 则表示结束前 程序 import decimal, re, operator parse轮input = re.compile(r'''(?x) # 允许 RE 中的注释和空白符 (\d+\.?\d*) # 带有可选的小数部分的数 \s* # 可选的空白符 ([-+/*]) # 运算符 $''') # 字符串结束 oper = { '+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv, } total = decimal.Decimal('0') def print轮total( ): print '== == =\n', total print """Welcome to Adding Machine: Enter a number and operator, an empty line to see the current subtotal, or q to quit: """ while True: try: tape轮line = raw轮input( ).strip( ) except EOFError: tape轮line = 'q' if not tape轮line: print轮total( ) continue elif tape轮line == 'q': print轮total( ) break try: num轮text, op = parse轮input.match(tape轮line).groups( ) except AttributeError: print 'Invalid entry: %r' % tape轮line print 'Enter number and operator, empty line for total, q to quit' continue total = oper[op](total, decimal.Decimal(num轮text)) 论 Python 的交互式解释器是很用的计算器,但一个简单的加法器是用的 举个例子,像 2345634+2894756-2345823 样的表达式很易读,所检查用于计算的 输入数并像想象的那简单而一个简单的加法器用一种很整洁的纵列的 方式显示数,样很容易地多检查你的输入外,decimal 模块使用一种 于十制的计算方式,那是们需要的,们很多时候并需要学家程师 或计算机所偏爱的浮点数学计算 在命行 shell 行脚本时脚本并是在 Python 交互式解释器中行而 134 第 3 计的,它会给出使用示,然一直等待输入敲入一个数一位或者多位数 ,加一个选的小数点,然是选的小数位数,紧跟一个算符/*- 和+, 4 个符在键盘的数找到,然回车脚本会根据算符将输入的 数计入到总数中你输入空行时,程序将打印出前的计算结果输入 q 并回车 时,程序会出种简单的输入输出的界面符合一个型的简单加法器的需求 decimal 包是 Python 2.4 标准的一个部如果你使用 Python 2.3, http://www.taniquetil.com.ar/facundo/bdvfiles/get轮decimal.html ,载并安装个包 decimal 支持高精度的十制数学算,相于制浮点算,它的用面更加广 泛如务的计算,然浮点算是计算机度快的算,是 Python 默认的算方式你再无花费时间和精力去学难于理解的制浮点算如 3.13 节所示,将默认的截断规则 ROUND轮HALF轮EVEN 按需求修改他规则 本节的码非常简单,很多性能的地方一个用的加强方法是,将外 界输入记录到硬盘备查用简单地做到一点质需要在循之前加一条语 ,打开一个文本文供添加信 tapefile = open('tapefile.txt', 'a') 然,在获得 tape轮line 的值的 try/except 语之,加一条语便将值写入文之中 tapefile.write(tape轮line+'\n') 如果做了面些操作,许你想继续加强 print轮total 函数,使得它既能够输出信 到命行窗口,时能将信写入到文中因,函数被修改 def print轮total( ): print '==== =\n', total tapefile.write('==== =\n' + str(total) + '\n') file 对象的 write 方法接一个符串作参数,但是并默认地在符串加换行, 和 print 语的行方式些,所们要明确地调用内建的 str 函数,明确地在 增加了一个’\n’,本的 print轮total 函数的第条语被改写 print >>tapefile, '==== =\n', total 些人非常厌种 print >>> somefile 的语法,但时种写法真的很方便,面的 用就是一个例子 一些能的改,如在每输入完算符之都需要按回车键,一的改 用按回车就直接行处理过意味着们需要处理无缓的输入,并 每要单个处理一个符,而能使用方便的于行的内建函数 raw轮input,参考 2.23 节供的一种跨的处理无缓输入的方法,再如增加一个 clear 函数或 者增加示功能,如果用户输入 0*会把结果清空 0,甚给个加法器增加一个 GUI 外壳过,准备把些部留给读者作 时间和务计算 135 本节实中非常要的一点是 oper ,它用算符/*-和+作键,用内建 的 operator 模块中的数学算函数作键的对值个功能用更冗长一点的方 法实,如,一长串 if/elif,如 if op == '+': total = total + decimal.Decimal(num轮text) elif op == '-': total = total - decimal.Decimal(num轮text) elif op == '*': ... and so on ... 过,Python 的用法明显更理想更方便,而会产生复性的码,更容 易维 更多资料 decimal 在 Python 2.4 的 Library Reference 中细绍,Python 2.3 的用户通过 载来使用, http://www.taniquetil.com.ar/facundo/bdvfiles/get轮decimal.html 参考十制 PEP 327, http://www.python.org/peps/pep-0327.html 3.15 检查信用卡校验和 感谢:David Shaw、Miika Keskinen 任务 需要检查一个信用卡是否遵循业标准 Luhn 校验和算法 解方案 Luhn mod 10 是信用卡业检验和的标准它是 Python 内建的算法,过们很 容易地实个算法 def cardLuhnChecksumIsValid(card轮number): """ 通过 luhn mod-10 校验和算法检查信用卡号 """ sum = 0 num轮digits = len(card轮number) oddeven = num轮digits & 1 for count in range(num轮digits): digit = int(card轮number[count]) if not (( count & 1 ) ^ oddeven): digit = digit * 2 if digit > 9: digit = digit - 9 sum = sum + digit return (sum % 10) == 0 136 第 3 论 本节码的原型来自于 Zope 中一个经再使用的电子商务程序 个程序完的简单验证作,你避免交一个错误的卡给信用卡供商, 而节省时间和金钱,因没人愿意验证一个错误的信用卡本节码的用面很 广,因很多府的身份认证码使用了 Luhnmodulus 10算法 一个完整的信用卡验证的套, http://david.theresistance.net/files/creditValidation.py 如果喜一行码解题,而是简洁清晰的编码风格,那认,(a)你能选 错了书Perl Cookbook 是那种会你满意的类型,(b)时,在想换本书之前,瞧瞧 面种写法能否你高 checksum = lambda a: ( 10 sum([int(y)*[7,3,1][x%3] for x, y in enumerate(str(a)[::-1])])%10)%10 - 更多资料 如果你确实喜个单行解题的校验和本,需要找个心理生 3.16 查看汇率 感谢:Victor Yongwei Yang 任务 你想周期性地用 crontab 或者 Windows 计划任务来行某 Python 脚本 Web 获 数据,监视某种币之间的兑换例,并在者之间的汇率达到某个阈值时发 醒邮 解方案 个任务和一系列的 Web 获数据的监任务很类似,它们包括了监视汇率化 监视股票行情气化等面们看看,根据加拿大银行的网站供的告一 个简单的 CSV逗隔开数值,易于解析,怎样实对美元和加元之间的汇率化 的监视 import httplib import smtplib # 在这里配置脚本的参数 thresholdRate = 1.30 smtpServer = 'smtp.freebie.com' fromaddr = 'foo@bar.com' toaddrs = 'your@corp.com' 时间和务计算 137 # 配置结束 url = '/en/financial轮markets/csv/exchange轮eng.csv' conn = httplib.HTTPConnection('www.bankofcanada.ca') conn.request('GET', url) response = conn.getresponse() data = response.read() start = data.index('United States Dollar') line = data[start:data.index('\n', start)] # 获得相关行 rate = line.split(',')[-1] # 行的最后字段 if float(rate) < thresholdRate: # 发送 email msg = 'Subject: Bank of Canada exchange rate alert %s' % rate server = smtplib.SMTP(smtpServer) server.sendmail(fromaddr, toaddrs, msg) server.quit() conn.close() 论 在处理外币时,自动完转换的能力是非常用的本节用一种简单而直接的方 式实了个功能 cron 行个脚本时,脚本先网站,获得 CSV,文 供了包括的 7 的新汇率 Date (m/d/year),11/12/2004,11/15/2004, ... ,11/19/2004,11/22/2004 $Can/US closing rate,1.1927,1.2005,1.1956,1.1934,1.2058,1.1930, United States Dollar,1.1925,1.2031,1.1934,1.1924,1.2074,1.1916,1.1844 ... 然脚本继续找特定的币United States Dollar并读一列的数据,获得 的汇率如果你得理解来困难,把它一一解开来,样能 会更清楚 US = data.find('United States Dollar') # 寻找货币的索引 endofUSline = data.index('\n', US) # 找到行末的索引 USline = data[US:endofUSline] # 切片获取一个字符串 rate USline.split(',')[-1] # 根据逗号切割并返回最后的字段 = 本节码供了一个要功能,汇率触阈值时,它会自动发邮醒用户 然个阈值随意置如置汇率跳出了预先置的阈值范围时发 邮醒 更多资料 Library Reference 和 Python in a Nutshell 中的 httplibsmtplib 和符串函数的相 文档 138 第 3 第 4 也消t起找n 引言 感谢:David Ascher,ActiveState,Learning Python 合著者之一 编程语言就像自然语言一对于通晓多种语言的人来说,每种语言都有自独的 一些属性和俄语和法语以奔放著,而英语以精确性和活力著像学院 派的法语,英语总是了满足使用的需而停地增加新词,如carjacking earwitnesssnailmailemailgooglewhacking和blogging在计算机语言 的世界中,Perl 以它心所欲的自由性而闻TMTOWTDIThere’s More Than One Way To Do It是 Perl 程序员对它的好的赞美在 Perl 语言和 APL 社中,简扼要被 认是一个非常要的优点如你在本书的各个节的中读到的那,作对, Python 程序员总是断表达他们对清和优的崇一个非常有的 Perl 高手经 跟说过,Python 确漂亮得多,但是 Perl 更好玩得意他,Python 对美学 的追无处在通过良好的计和定,而 Perl 表达了救药的幽默感 之所以要在引言开头提到些看去题毫无联的语言性,是因本的内 容将会 Python 的计美学和语言的活力如果本书是于 Perl 的,那类似于 本于语言窍门的内容,多半会读者挠头深思,然发出一个啊哈的 赞叹,说定会哈哈大笑,因读者会慢慢会到各个小把面的才思想作 对,在本的大部内容中,作者都在尝试展示语言的优性,因他认 美好的东西没有引足够的注意和赞美就像一,是温哥华的一个骄傲的 居民,会自告奋勇地带着旅游者,向他们展示个美城的所有的美好的地方, 公园到海滩,再到山峦,而一个 Python 用户能会到他的朋和, 地说,你一定得瞧瞧个!对于和一些了解的程序员来说,在 Python 中写程序 有时候是一种喜悦的享,而是一种迫于的追学它的新性,赏它的 计,它的优,它的洗,完全是一种乐趣,但时教它的各种节,讲解它的 微妙用法,是一种极大的快乐 国国 139 得提一提本的历初在第 1 定各内容时,们认有一系列的 节,每内容都基于的目的—如,蛋奶酥果馅饼炖小牛肘那些节 都自然地各个的类别,如甜食开胃小食肉食,或者没那美好的, 人没食欲的文,算法等所以,们选了一系列能的类,并在 Zope 的网站 内容,之完全地打开了思潮的闸门 结果,很快们就发有一些内容无法入任何预先置的类是以解释的,拿烹饪 来说本的内容,以用制作掺油面糊来做喻如果你没有法式烹饪的背,要略 作解释,是一种面粉和油脂的混合物,一般用于制作酱,捏面团加面加入蛋清 在锅翻面烫煮,些常的和知识,任何一个合的厨师都知道,但是任何一 本菜谱都会有些知识和常常被用来准备菜肴,但难于档到任何一种菜肴的 制作方法如果你是一个新手厨师,想尝试某个很棒的菜谱,会铩羽而,那是因菜 谱的作者经假你备那些要的知识了作者们会在类似 Cooking for Divorced Middle-Aged Man 的书中解释那些要的知识而通常配插图但们并想把些 的内容书中剔除,所以,新的一就生了抱歉,没有配精美的插图 在本书第 1 的本的引言中,是说的 相信本中的内容是对时间敏感的部是因些和窍门看去和 的语言性有较高的联度 的预言感到骄傲,的说法完全确新本的内容,完全注前的语言 点,初的一些早期内容经再适用了在本书第 1 发行之,Python 经发 了个要的本,Python 2.3 和 Python 2.4,门语言的发展使得初期的一些内容 需要适新性和新的函数,语言本身得更简洁紧凑和强大,时有无数新出 的有趣的节值得们去发掘 总之,本约有一半内容和本书的例差多是全新的,外一半对第一的 内容行了改编大多是简化由于些简化作,以把本书的点放到仅有的 个语言本2.3 和 2.4之,而再考虑和覆盖第 1 的很多的语言本, 本第 1 的内容多出了超过 1/3,和本书的总的内容增幅差多 值得注意的是,很多在本中经过改编的内容都一些基本的,没有改的语言性 赋值的语绑定复制引用序列所有些都是行 Python 编程的键的要 素,过有点疑惑,知道 Python 在以几的发展中会会考虑改动些键性 4.1 对象拷贝 感谢:Anna Martelli Ravenscroft、Peter Cogolo 140 第 4 任务 你想拷贝某对象过,你对一个对象赋值,将作参数传递,或者作结果返 回时,Python 通常会使用指向原对象的引用,并是真的拷贝 解方案 Python 准的 copy 模块提供了个函数来建拷贝第一个常用的函数做 copy, 它会返回一个有的内容和属性的对象 import copy new普list = copy.copy(existing普list) 某些殊的时候,你能会需要对象中的属性和内容被别地和递地拷贝,以使 用 deepcopy import copy new普list普of普dicts = copy.deepcopy(existing普list普of普dicts) 论 给一个对象赋值或者将作参数传递,或者作结果返回时时,Python像 Java 一使用了一个指向原对象的引用,并是真的拷贝他一些语言在每 赋值时都行拷贝操作Python 来赋值操作行式的拷贝要得到一 个拷贝,确地要,需要的是拷贝 Python 的行模式既简单又快,而很一如果需要拷贝但确地要,很 有能会遇到麻烦个例子 >>> a = [1, 2, 3] >>> b = a >>> b.append(5) >>> print a, b [1, 2, 3, 5] [1, 2, 3, 5] , a 和 b 都引用到的对象列表 a,所以,无论们通过哪个修改 了对象的内容,之,无论通过哪个来查看对象,修改结果都是一的个过 程中,并没有一个原始的,被修改的拷贝 警告 要想成为一个好的 Python 程序员,必须了解修改对象和将对象赋值给 变量的区别,赋值使用的是引用。这两种操作相互之间并没有什么关 联。类似于 a=[ ]这样的语句,是对名字 a 做了重新绑定,但却不会影 响原先绑定到 a 的对象。因此,这里完全没有引用和拷贝的问题:只 有当需要修改某些对象的时候,引用和拷贝才有可能成为问题。 Python 141 如果想修改一个对象,但又需要改动原对象,做一个拷贝如前面提 到的,Python 准的 copy 模块提供了个函数来制作拷贝一般情况, 以使用 copy.copy,它完的是对一个对象的浅拷贝—虽然生了一个新对 象,但是对象内部的属性和内容然引用原对象,的操作度很快而节省 内 如果想原对象真地复制一个全新的对象,或者想修改的是对象内部的属性和 内容,而是对象本身,那浅拷贝能满足你的需 >>> list普of普lists = [ ['a'], [1, 2], ['z', 23] ] >>> copy普lol = copy.copy(lists普of普lists) >>> copy普lol[1].append('boo') >>> print list普of普lists, copy普lol [['a'], [1, 2, 'boo'], ['z', 23]] [['a'], [1, 2, 'boo'], ['z', 23]] , list普of普lists 和 copy普lol 指向了个的对象个列表,所以们以 别修改它们而互相影响然而,list普of普lists 中的元素是 copy普lol 的对元 素,所以无论通过哪个,一们通过索引修改了元素,以无论通过哪个 问内容,们会看到修改经对者时生效了 所以,如果需要拷贝一些容器对象,递地拷贝内部引用的对象包括所有 的元素属性元素的元素元素的属性等,使用 copy.deepcopy 种深拷贝操作, 会耗相的时间和内,但如果深拷贝确是需要的效果,你别无选择而要 深拷贝,copy.deepcopy 是唯一用的方法 对于通的浅拷贝,你能会需要外一些方法的功能,然,假你知道 想拷贝的对象的类型对于列表 L,调用 list(L)对于 d,调用 dict(d)了拷 贝合 sPython 2.4 中经引入了内建的类型 set,调用 set(s)由于 listdict 和 2.4 中的 set 经是内建的了,你无做任何准备作就以直接使用你 在能够会到种通用的方法了了拷贝一个被拷贝copyable的 对 象 o, 对象属于内建的 Python 类型 t,以简单地调用 t(o)来建拷贝提供了 一个浅拷贝方法d.copy(),它做的情和 dict(d)完全一过在者之中, 建议你选择 dict(d)种方式以和他类型的拷贝方式统一来,而短一些, 少一个符 对于任意类型或者类的例,无论是自编写的类或者中引入的类,一般用 copy.copy 就行了如果是自编写的类,通常值得门定一个 copy 或者 clone 方法,如果想定制自的类的浅拷贝方式,以提供一个殊的普 普copy普 普方法于 个方法的节参看 6.9 节,或者殊的普 普getstate普 普和普 普setstate普 普方法参 考 7.4 节中于些殊方法的节,些方法有助于深拷贝和序列化—如对 例行 pickling 操作—的如果想自的类的独有的深拷贝方式,需要提 供一个殊的普 普deepcopy普 普方法参考 6.9 节 142 第 4 注意,没有要拷贝那些改的对象符串数元等,因完全担 心会经意改动它们如果尝试行拷贝操作,然会得到原对象,然会有 大害处,过浪费了一些时间和代码个例子 >>> s = 'cat' >>> t = copy.copy(s) >>> s is t True is 操作符检查个对象是相,而是相等is 检查对象是相==操作符检 查个对象是相等对于改的对象来说,检查是相几乎没有什用处 们是了展示对改对象调用 copy.copy 是没意的,但是无害的对 于改的对象,检查相性有时是要的个例子,假你确定个 a 和 b 是别指向的对象是引用一个对象,以用 a is b 一条简单快 的检查语来到答案需要确保原对象被改时,就以考虑对原对象行拷 贝操作了 警告 可以使用其他的方法来创建拷贝。给定一个列表 L,使用“整个 对 象 的 切片”L[:]以及列表推导 [x for x in L]都可以完成对 L 的浅拷贝,而使用 添加空列表, L+[],以及用 L 乘以 1,L*1,诸如此类的方式都只是无 谓的浪费时间和内存,直接调用 list(L)速度更快也更清晰。不过,由于 某些历史原因以及应用的广泛性,你也应该熟悉 L[:]这种用法。所以, 即使我不建议你在自己的代码中这么写,但你也可能在别人的代码中 看到这种用法。 类似的,给定一个字典 d,也可以通过循环来创建一个浅拷贝 d1: >>> d1 = { } >>> for somekey in d: ... d1[somekey] = d[somekey] 或者更简明的方式: d1 = { }; d1.update(d)。不过,这仍然是一种时间和 代码上的浪费,除了增加一些迷惑性和降低速度,没有更多的东西。 用 d1=dict(d)这样一句话就可以了,又快又轻便。 更多资料 Library Reference 和 Python in a Nutshell 中于 copy 模块的内容 Python 143 4.2 通过列表构建列表 感谢:Luther Blissett 任务 你想通过操作和处理一个序列或他的迭代对象中的元素来建一个新的列表 解方案 假你想通过给某个列表中的每个元素都加 23 来构建一个新列表用列表以 很直接地个想法 thenewlist = [x + 23 for x in theoldlist] ,假需要用某列表中的所有大于 5 的元素来构一个新列表用列表代码 以写 thenewlist = [x for x in theoldlist if x > 5] 你试图将种想法合一的时候,以增加一个 if 子,并对选定的使用某 些表达式,如加 23,些都以用一行概括 thenewlist = [x + 23 for x in theoldlist if x > 5] 论 优清和务,都是 Python 的心值,列表说了点是怎和谐地 统一来的,你直觉地考虑改某列表而是新建某列表时,列表 常常是好的办法如,假如需要将某列表 L 中的所有大于 100 的元素置 100, 好的方法是 L[:] = [min(x,100) for x in L] 面代码在给一个整个列表的赋值的时,修改了列表对的数据,而 是试图对 L 新绑定,如写 L = … 你是需要执行一个循的时候,使用列表如果需要循,那就写相 的循代码于循列表的例子,参看 4.4 节第 19 会有更多的有 Python 的迭代的内容 外,如果 Python 有内建的操作或者类型能够以更直接的方式你的需,你 要使用列表如,了复制一个列表,用 L1 = list(L)就好,要用 L1 = [x for x in L] 144 第 4 类似地,如果想对每个元素都调用一个函数,并使用函数的返回结果,用 L1=map(f, L),而是 L1 = [f(x) for x in L]过大多数情况,种列表用法 没问题 在 Python 2.4 中,序列过长,而你每需要获一个元素的时候,考虑使用生 器表达式,而是列表生器表达式的语法和列表一,过生器 表达式是被()圆括括的,而是方括[]如,如果们需要计算本节解方案 的列表的某些元素之和,在 Python 2.3 中以做 total = sum([x + 23 for x in theoldlist if x > 5]) 在 Python 2.4 中,代码以写得更自然一点,方括以省略用增加多余的圆 括—有调用内建的 sum 函数的圆括就足够了 total = sum(x + 23 for x in theoldlist if x > 5) 除了更加简洁,个方法以避免一性将整个列表载入内,而在列表别长 的时候,在一定程度提高处理度 更多资料 参考 Reference Manual 中 list display列表的一个和 Python 2.4 中生器 表达式的内容第 19 Library Reference 和 Python in a Nutshell 中 itertools 模块和内 建的 mapfilter 和 sum 函数以 Haskell, http://www.haskell.org 警告 Python 从函数式语言 Haskellhttp://www.haskell.org借来了列表推 导,当然在语法和关键字上有一些差异,而不是完全一样。如果你 懂 Haskell,那么小心点,因为 Haskell 的列表推导正如这门语言的 其余部分一样,使用惰性求值 lazy evaluation的方式也被称为 正则序normal order或按需调用 call by need。每一项都只在 真正需要的时候才计算。Python,像很多语言一样,使用包括列 表推导和其他部分主动求值 eager evaluation方式也被称为应 用序 application order,按值调用 call by value,或者严格求值 strict evaluation。也就是说,整个列表在列表推导执行的时候就 完成了计算,而且结果被尽量长久地保存在内存中以供以后使用。如 果你正在将一个 Haskell 程序改写成 Python 程序,而且这个 Haskell 程序用列表推导来表示无限序列或任何一个很长的序列。那么 Python 的列表推导可能不是很合适。不过 Python 2.4 中出现了生成 器表达式,它的语义更加接近 Haskell 的惰性求值方式,同样是按需 调用,所以它应该是更好的选择。 Python 145 4.3 若列表中某元素在返回之 感谢:Nestor Nissen、A. Bass 任务 你有一个列表 L,有一个索引 i,你希望 i 是 L 的有效索引时获 L[i],若是 有效索引,返回一个默认值 v如果 L 是,以使用 L.get(i, v)来满足需, 是列表并没有 get 个方法 解方案 很显,们得自写个函数,在,简单直接的方法就是好的方法 def list普get(L, i, v=None): if -len(L) <= i < len(L): return L[i] else: return v 论 解方案中的函数据 Python 的索引规来检查 i 的有效性有效索引能在大于等 于-len(L)和小于 len(L)个间中但如果所有传递给 list普get 函数的参数 i 都是有效 的索引,你能会喜外一种方式 def list普get普egfp(L, i, v=None): try: return L[i] except IndexError: return v 但是,除非传递给函数的索引绝大多数都是有效索引,个函数通过某些测 试测量将会解方案中的 list普get 函数慢 4 倍因,个获得原谅总是 获得许容易easier to get forgiveness than permission,EGFP的函数,虽然更有 Python 的精神和风,但在种殊的情况,并值得荐 试过几个看去更漂亮更复和更迷惑人的方法,过,除了更加难于解释和 理解之外,它们无一例外地那个朴无华的 list普get 函数慢给出一个通用的准 你写 Python 程序时,倾向于清和读性,而是紧凑和精炼—选择简 单,而是精要你坚持做,你常常会发你的代码跑得更快,而更强 健,更易于维护在真世界中,对于 99.9%的用而言,遵循个原要获得一点 度提升要的多 更多资料 Language Reference 和 Python in a Nutshell 中于列表索引的文档 146 第 4 4.4 循问序列中的元素和索引 感谢:Alex Martelli、Sami Hangaslammi 任务 需要循问一个序列,并每一都需要知道经问到的索引因需要新绑 定序列的入口,但 Python 提供的首选的循方式完全用依赖索引 解方案 内建函数 enumerate 是而生看例子 for index, item in enumerate(sequence): if item > 23: sequence[index] = transform(item) 它看去很净易读,而那种通过索引问元素的方式快 for index in range(len(sequence)): if sequence[index] > 23: sequence[index] = transform(sequence[index]) 论 循遍历一个序列是很常的需,Python 强烈建议你用一种直接的方式 是有 Python 风的问序列中每个元素的方式 for item in sequence: process(item) 而他一些型的较层的语言,是用种直接的循方式,而是通过序列的索 引,据索引到每一个对的子 for index in range(len(sequence)): process(sequence[index]) 直接的循方式更加净更易读更快,而更通用因据定,法以 用于任何迭代对象,而据索引问的方式适用于序列,如列表 但是,有时候在循中,你的确需要时获得索引和索引对的子一个常的理 由是,你想新绑定列表的新入口,将 thelist[index]赋值一个新的子了支 持种需,Python 提供了内建函数 enumerate,它接任何迭代的参数,并返回一 个迭代器,迭代器产生的是一个个子的元形如(index, item)的结果,一一 因你的 for 子的头部以写 for index, item in enumerate(sequence): Python 147 ,在 for 的体中,索引和子都是以问的 了帮助你记 enumerate 产生的结果,考虑惯用法 d=dict(enumerate(L))某 种意来讲,用法获得的 d 是等于列表 L 的,因对于任意一个有效的非 负索引 i,d[i] is L[i]都立 更多资料 Library Reference 和 Python in a Nutshell 中 enumerate 的相内容第 19 4.5 在无共享引用的条建列表的列表 感谢:David Ascher 任务 你想建一个多维度的列表,时避免式的引用共享 解方案 了建列表避免式地共享引用,们使用列表个例子,建一个 5×10 的全 0 的阵列 multilist = [[0 for col in range(5)] for row in range(10)] 论 Python 新手首发列表乘以一个整数得到的列表是原列表的多复时,他会感 到非常奋,因看去种处理极优个例子 >>> alist = [0] * 5 很显,是很棒的一种获得 1应5 维度的全 0 的数的方法 问题是,一维的任务常常会据需要扩展维的,所以,一个自然的想法是行如 的处理 >>> multi = [[0] * 5] * 3 >>> print multi [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]] 看去它作完全常,但是新手们常常会发自被 bug 困扰个简单的例子, 面的代码段很有说服力 >>> multi[0][0] = 'oops!' >>> print multi [['oops!', 0, 0, 0, 0], ['oops!', 0, 0, 0, 0], ['oops!', 0, 0, 0, 0]] 148 第 4 绝大多数程序员都少在个问题过一跟头于 http://www.python.org/ doc/FAQ.html#4.50 的 FAQ 条目了理解个问题,以将多维列表的建解 个骤 >>> row = [0] * 5 # row 列表中的 5 个子项都引用 0 >>> multi = [row] * 3 #multi 列表中的 3 个子项都引用 row 解过的代码段生的 multi 列表完全等于前面的更简洁的[[0]*5]*3 代码段产 生的结果,而问题完全一如果你给 multi[0][0]指定一个新值,会改 multi[1][0] 的值,multi[2][0]的值…,甚改了 row[0]的值! 代码中的注释是理解个混淆的键之处将序列和数相乘产生的新序列,将含有 多个引用原始内容的子,数量等于乘数在 row 的建中,有无引用被复制完全 要,因被引用的是数,而数是改的换说,如果对象是改 的,对象和对对象的引用没什别第行,们建了一个新的列表, 中包含了 3 个对[row]的内容的引用,而内容是对一个列表的引用因,multi 包含了 3 个对列表的引用, multi 的第一个子的第一个子被修改的时候, 你会发共享的列表的第一个子被修改了奇迹就是发生的 而解方案中所示的列表避免了问题使用列表,没有任何引用共享的问 题—你完了一个真的嵌套计算如果确会了面的论,你能会意识 到以用内层的列表,需要外层的换说,代码改一是是以? multilist = [[0]*5 for row in range(10)] 答案是,以,在内层使用列表乘法,而在他层使用列表,度会 快少—要示例快倍那什荐论的种方法呢?答案是 对于个例子,在 Python 2.3 中,度的提升是 57μs 降到了 24μs,在 Python 2.4 中, 49μs 降到了 21μs,行是一又老又破的个人计算机AMD Athlon 1.2 GHz CPU,行 Linux优化一个列表的建操作,而节省几十μs 并会对你的 用程序的性能产生什大的影响如果你真的需要优化,那就去真要的地 方,真对用程序的整体性能产生大影响的地方所以,更倾向于解方案中 给出的代码,它更加简单,因它内层到外层的列表建都使用了相的结构,看 去更有对性,然更易读 更多资料 Library Reference 和 Python in a Nutshell 中的内建函数 range 的文档 4.6 展开一个嵌套的序列 感谢:Luther Blissett、Holger Krekel、Hemanth Sethuram、ParzAspen Aspen Python 149 任务 序列中的子能是序列,子序列的子能是序列,以类,序列嵌套以 达到任意的深度需要循遍历一个序列,将中所有的子序列展开一个单一的 有基本子的序列一个基本子或者原子,以是任何非序列的对象—或者 说子,假如你认嵌套序列是一棵树 解方案 们需要能够判断哪些们在处理的子是需要被展开的,哪些是原子了获得 通用性,们使用了一个断定来作参数,由它来判断子是是以展开的断定 [predicate]是一个函数,每们处理一个元素时就将用于元素并返回一个尔 值在,如果元素是一个需要展开的子序列就返回 True,返回 False们 假定每一个列表或者元都是需要被展开的,而他类型是那简单的解 方法就是提供一个递的生器 def list普or普tuple(x): return isinstance(x, (list, tuple)) def flatten(sequence, to普expand=list普or普tuple): for item in sequence: if to普expand(item): for subitem in flatten(item, to普expand): yield subitem else: yield item 论 展开一个嵌套的序列,或者等地,按照序遍历一棵树的所有子,是在各种 用中很常的任务如果有一个嵌套的结构,元素都被序列或者子序列,而 ,基于某些理由,你并心结构本身,需要的是一个接一个地处理所有的元素 个例子 for x in flatten([1, 2, [3, [ ], 4, [5, 6], 7, [8,], ], 9]): print x, 输出 1 2 3 4 5 6 7 8 9。 个任务唯一的问题是,怎在量通用的尺度,判断什是需要展开的,什是 需要被做原子的,没有看去那容易所以,绕开直接的判断,把个 作交给了一个调用的断定参数,调用者以将传递给 flattern,如果调用者满足 于 flatten 简单的默认行方式,展开元和列表 在 flatten 所在的模块中,们需要提供一个调用者能需要用到的断定—它将 150 第 4 展开任何非符串无论是通符串是 Unicode的迭代对象符串是迭代 的,但是绝大多数用程序是想把它们原子,而是子序列 于判断对象是迭代,们需要对对象调用内建的 iter 函数若对象是 迭代的,函数将抛出 TypeError 常了判断对象是是类符串的,们简单 地检查它是是 basestring 的例, obj 是 basestring 的任何子类的例时, isinstance(obj, basestring)的返回值将是 True—意味着任何类符串类型因, 的一个断定并难写 def nonstring普iterable(obj): try: iter(obj) except TypeError: return False else: return not isinstance(obj, basestring) 体的需是展开任何迭代的非符串对象时,调用者以选择调用 flatten(seq, nonstring普iterable)无疑,把 nonstring普iterable 断定作 flatten 的默认选是一个更 好的选择在简单的需中,如们前面展示的示例代码段,使用 nonstring普iterable 会使用 list普or普tuple 慢 3 倍以 们以写一个非递本的 flatten种写法以超越 Python 的递层的极限, 一般超过几千层无递遍历的要点是,采用一个确的先出LIFO 在个例子中,们以用迭代器的列表 def flatten(sequence, to普expand=list普or普tuple): iterators = [ iter(sequence) ] while iterators: # 循环当前的最深嵌套最后的迭代器 for item in iterators[-1]: if to普expand(item): # 找到了子序列,循环子序列的迭代器 iterators.append(iter(item)) break else: yield item else: # 最深嵌套的迭代器耗尽,回过头来循环它的父迭代器 iterators.pop( ) 中 if 语块的 if 子会展开每一个们需要展开的元素—们需要循遍历 的子序列所以在子中,们将那个子序列的迭代器压入的,再通过 break 打断 for 的执行,回到外层的 while,外层 while 会针对们压入的新的迭代器执 行一个新的 for 语else 子用于处理那些需要展开的元素,它直接产生元素 本身 Python 151 如果 for 循被打断,for 语块所属的 else 将得以执行—换说, for 循完全执行完,说它经遍历完前的新的迭代器所以,在 else 子中, 们移除了经耗的嵌套深的迭代器,之外层的 while 循继续执 行,如果经空了,中循,如果中有迭代器,执行一个新的 for 循 来处理之—好是执行中断的地方,本质,迭代器的任务就是记迭代 的状态 flatten 的非递产生的结果和前面的简单一些的递本的结果完全一如果 你认非递会递方式快,那你能会失望采用了一系列的测试用例 行察测量,发非递本要递本慢约 10% 更多资料 Library Reference 和 Python in a Nutshell 中于序列类型和内建的 iterisinstance 以 basestring 的文档 4.7 在行列表中完对列的删除和排序 感谢:Jason Whitlark 任务 你有一个包含列表行的列表,在你想获得一个列表,列表包含了的行, 但是中一些列被删除或者新排序了 解方案 列表很适合个任务假你有 listOfRows = [ [1,2,3,4], [5,6,7,8], [9,10,11,12] ] 需要一个有行的列表,但是中第列被删除了,第和第四列互换了置 们用一个简单的列表来完任务 newList = [ [row[0], row[3], row[2]] for row in listOfRows ] 有一个方法有操作性,许更优一些,采用一个辅助的序列列表 或元,序列的列索引置好是需要的序,在外层对 listOfRows 行循 操作的列表的内部,又加入了一个嵌套的对辅助序列行循操作的内层列表 newList = [ [row[ci] for ci in (0, 3, 2)] for row in listofRows ] 152 第 4 论 一般用列表的列表来代表维的阵列把些列表看做以维阵列的行元 素的列表常常需要处理种维阵列的列,一般是新排序,有时会忽略列中 的部内容管列表在别处大显神通,但粗略一看,它在似乎用处大 少的第一感觉是 列表会产生一个新的列表,而是修改有的列表需要修改一个有的列表 时,好的办法是将有列表的内容赋值一个列表个例子,假要修改 listOfRows,对于本节的任务,以写 listOfRows[:] = [ [row[0], row[3], row[2]] for row in listOfRows ] 如本节解方案的第个例子所示,以考虑使用一个辅助的序列,中包含的 列索引按需要的序排列,第一个例子用硬编码指定序要好你能对嵌套的 列表感到放心,但它你想象的更简单安全如果采用解方案中的第 种方式,会时获得更多的通用性,因以给辅助序列一个,并使用个 来对一些行列表行新排序,或者将作参数传递给一个函数,等等 def pick普and普reorder普columns(listofRows, column普indexes): return [ [row[ci] for ci in column普indexes] for row in listofRows ] columns = 0, 3, 2 newListOfPandas = pick普and普reorder普columns(oldListOfPandas, columns) newListOfCats = pick普and普reorder普columns(oldListOfCats, columns) 例所做的情和本节前面的代码段所做的情完全一,过它操作个独立的 的列表,并别获得个对的新的列表追大程度的通用性是一种 难以抵御的诱惑,但在,个 pick普and普reorder普columns 所表出的通用性似乎 恰到好处 一条,在前面用到的一些函数中,一些人喜用看去更漂亮的方式来表示内 层列表,而采用简单直接的方式,如 [row[ci] for ci in column普indexes] 他们喜用内置的 map 函数以 row 的殊的普 普getitem普 普方法常被用作被绑定方法 来完索引子任务,因他们编写代码 map(row.普 普getitem普 普, column普indexes) 体到某些的 Python 的本,种看去漂亮但又有点人困扰的方法能度 会快一些但然认体简洁性的列表是佳方式 更多资料 Language Reference 和 Python in a Nutshell 中的列表的文档 Python 153 4.8 维阵列换 感谢:Steve Holden、Raymond Hettinger、Attila Vàsàrhelyi、Chris Perkins 任务 需要换一个列表的列表,将行换列,列换行 解方案 需要一个列表,中的每一都是长度的列表,像 arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]] 列表提供了简单方便的方法以完维阵列的转换 print [[r[col] for r in arr] for col in range(len(arr[0]))] [[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]] 一个更快更人困惑的方法输出是一的是利用内建函数 zip 的 print map(list, zip(*arr)) 论 本节展示了一种简洁而清的转换方式,有一个更快的备选方案在需要简洁和 清并的时候,列表通常是很好的选择,而备选方案利用内建函数 zip 以外一 种方式达到目的,显得很晦涩难懂 有时,你获得的数据的序是确的个例子,如果使用微软的 ActiveX Data ObjectsADO数据接口,由于 Python 和微软的首选语言Visual Basic在对 数元素排序的差,Getrows 方法返回的是 Python 中的列本节针对种 常需提出的种解方案,你有机会在清和度之间行选择 在列表的解方案中,内层改的是行中选出的元素,外层影 响选择子selector,列由转换 而基于 zip 的解方案,们使用了*a 语法将 arr 中的每个元素行,据序,作 隔开的参数传递给 zipzip 返回的是元的列表,经完了转换通过 map 调用,们以对每个元调用 list,以获得一个列表的列表既然们能将 zip 的 结果直接做列表使用,们以通过使用 itertools.izip 来得到一点改因 izip 并 会将结果做列表载入内,而是每生一个子 import itertools print map(list, itertools.izip(*arr)) 过,对个例子而言,一点度提升许并能抵它所带来的复性 154 第 4 *args 和**kwds 语法 *args*通常紧跟一个标识符,你会看到 a 或者 args 都是标识符是 Python 用于 接收或者传递任意基于位置的参数的语法。当你接收到一个用这种语法描述的参 数时比如你在函数的 def 语句中对函数签名使用了星号语法 ,Python 会将此标 识符绑定到一个元组,该元组包含了所有基于位置的隐式地接收到的参数。当你 用这种语法传递参数时,标识符可以被绑定到任何可迭代对象事实上,它也可 以是任何表达式,并不必须是一个标识符,只要这个表达式的结果是一个可迭代 对象即可 。 **kwds标识符可以是任意的,通常用 k 或者 kwds 表示是 Python 用于接收或者传 递任意命名的参数的语法。 Python 有时候会将命名参数称为关键字参数,它们其实 并不是关键字 —只是用它们来给关键字命名,比如 pass、for 或 yield,还有很多。 不幸的是,这种让人疑惑的术语目前仍是这门语言及其文化的根深蒂固的一个组成 部分。 当你接收到用这种语法描述的一个参数时比如你在函数的 def 语句中对函 数签名使用了双星号语法, Python 会将标识符绑定到一个字典,该字典包含了所有 接收到的隐式的命名参数。当你用这种语法传递参数时,标识符只能被绑定到字典 其实它也可以是表达式,不一定是一个标识符,只要这个表达式的结果是一个字典 即可。 当你在定义或调用一个函数的时候,必须确保 *a 和**k 在其他所有参数之后。如果这 两者同时出现,要将 **k 放在*a 之后。 如果要转换非常大的数阵列,以考虑 Numeric Python 和他的第方包Numeric Python 支持一系列换以轴旋转,些数学转换能把大多数人绕晕 更多资料 Reference Manual 和 Python in a Nutshell 中 list displays列表的一种法以 对于基于置的参数*a 和命参数**k 的传递内建函数 zip 和 mapNumeric Python http://www.pfdubois.com/numpy/ 4.9 中值 感谢:Andy McKay 任务 你想中值,但是又想由于你搜的键在而处理常 Python 155 解方案 的 get 方法是值而准备的假你有一个 d = {‘key’:’value’, }了得 到 key 在 d 中对的值,希望担心常的问题,以编写代码 print d.get('key', 'not found') 如果想在值之将条目删去,用 d.pop执行 get 和 remove 操作换 d.get 读 d,修改 d 的值 论 了在键在的时候值并引发常,用的简单的 get 方法 如果试图通过索引的方式值,如 d[x],而 x 并是 d 的键,你的动会引发 KeyError 常通常没什问题如果期望获中 x 对的值,常是好 的提醒你所犯的错误的方式如,能需要调试你的程序 然而,有时候是想尝试一,因你经知道,x 能是 d 的键种情况, 用引入 in 测试,如 if 'key' in d: print d['key'] else: *args 和**kwds 语法 print 'not found' 或者使用 try/except 语,如 try: print d['key'] except KeyError: print 'not found' 而使用 get 方法,就像解方案所示的那如果调用 d.get(x),会有任何 常抛出如果 x 是 d 中的键,你会得到 d[x],如果是,你能得到 None 以检查或者继续传递 x 是 d 的键的时候,如果 None 是你期望的值,以 调用 d.get(x, somethingelse),如果 x 是 d 的键,得到的值是 somethingelse get 是一种简单而有用的机制,Python 的文档对有很好的解释,奇怪的是有相多的 人并清楚一点一个类似的方法是 pop, get 很类似,过键在中时, pop 会时删除条目有一条加说get 和 pop 并完全对如果 x 是 d 的键,d.pop(x)会抛出 KeyError 常如果要想获得和 d.get(x)的效果,时 有删除条目的能力,调用 d.pop(x, None) 更多资料 4.10 节Library Reference 和 Python in a Nutshell 中于映射类型的文档 156 第 4 4.10 给增加一个条目 感谢:Alex Martelli、Martin Miller、Matthew Shomphe 任务 给定一个 d, k 是的键时,你想直接使用 d[k],若 k 是 d 的键,个操 作会给增加一个新条目 d[k] 解方案 的 setdefault 方法是而计的假们在建一个由单词到页数的映 射,将把每个单词映射到个词出过的页的页码构的列表个用中键 的代码段能是的 def addword(theIndex, word, pagenumber): theIndex.setdefault(word, [ ]).append(pagenumber) 段代码等于面的更加的本 def addword(theIndex, word, pagenumber): if word in theIndex: theIndex[word].append(pagenumber) else: theIndex[word] = [pagenumber] 以 def addword(theIndex, word, pagenumber): try: theIndex[word].append(pagenumber) except KeyError: theIndex[word] = [pagenumber] 使用 setdefault 方法能在相大的程度简化 论 对于一个 d,d.setdefault(k, v)非常接于 d.get(k, v),者在前面的 4.9 节介绍过 本质的别是,如果 k 是的键,setdefault 方法会将 d[k]赋值 v, d[k]=v get 仅仅返回 v,对 d 会有任何影响因,需要类似于 get 的功能,但又需 要时提供种殊的效果时,考虑 setdefault 方法 如果中的值是列表,setdefault 方法尤有用,4.15 节会提供更多的节经 的 setdefault 用大概会是 somedict.setdefault(somekey, [ ]).append(somevalue) Python 157 对于改的值,setdefault 方法就没那有用了如果想对单词计数,好的方法 是使用 get,而是 setdefault theIndex[word] = theIndex.get(word, 0) + 1 是因反你要将的 theIndex[word]的条目新绑定因数是改 的过,针对们个单词到页码的例子,你一定想由于面的写法而 性能的损失 def addword(theIndex, word, pagenumber): theIndex[word] = theIndex.get(word, [ ]) + [pagenumber] 们看看个本的 addword,每调用它的时候都将建 3 个新的列表一个 被作第个参数传递给 theIndex.get 的空列表,一个含有一个元素 pagenumber 的列 表,以将前面者拼接而得到的有 N+1 个元素的列表N 是 word 之前出的数 建多列表很显然会降整体性能个例子,在的计算机,对出在 1 000 页中的 4 个单词所行的索引操作计时用 addword 的第一个本来作参考,第个 本使用 try/except快了约 10%,第个本使用 setdefault慢了约 20%— 种性能差基本无紧要但第四个本使用 get慢了 4 倍,的性能损失恐 怕就能视若无睹了 更多资料 4.9 节4.15 节Library Reference 和 Python in a Nutshell 中的相文档 4.11 在无过多援引的情况建 感谢:Brent Burley、Peter Cogolo 任务 你逐渐惯了 Python,会发自经建了大量的键是识符时,以 用 dict 加命的参数来避免援引它们 data = dict(red=1, green=2, blue=3) 看去直接用形式的语法要整洁一些 data = {'red': 1, 'green': 2, 'blue': 3} 论 一种建的好方法是调用内建的 dict 类型,但有时用带有花括和冒的形 式错本节的代码说,如果键都是文的符串并在语法对用户而言是 有效适合作识符的,通过调用 dict,无援引的键能对像12ba和 158 第 4 for一的符串用法,因12ba以数开头,而 for 好是 Python 键, 能作识符 形式的语法是 Python 中唯一需要用到花括的地方如果喜花括,或者 好所用的键盘容易打出花括所有的意大利局的键盘都,那以用 dict( )而是{}来建一个空 调用 dict 能给你带来一些他额外的好处dict(d)返回一个完全独立于原 d 的拷 贝,就像 d.copy()一但 d 是一个,而是一个数对(key, value)的序列时,dict(d) 然有效如果 key 在序列中出多,那有一出的 key 会被计入一 般建的操作大概是 d = dict(zip(the普keys, the普values)) the普key 是键的序列,the普values 是键的对值的序列内建的 zip 函数建并返回一 个数对(key, value)构的列表,内建类型 dict 接个列表作参数并建相的 如果序列非常长,那用 Python 准中的 itertoos 模块能有效地提高度 import itertools d = dict(itertools.izip(the普keys, the普values)) 内建函数 zip 会在内中建出包含所有数对的列表,而 itertools.izip 一生一对 数据在的计算机,对于长度 10 000 的序列,面种方式以快倍— Python 2.3 中是 18ms 对 45ms,Python 2.4 中是 17ms 对 32ms 以在 dict 调用中使用基于置的参数和命参数如果命的参数好和基于 置的参数突,前者生效个例子,面是一个的建,中用到了前面提 到的 Python 键和一个做命参数的键 d = dict({'12ba':49, 'for': 23}, rof=41, fro=97, orf=42) 如果想建一个,中每个键都对相的值,需调用 dict.fromkeys (key普 sequence, value)如果你忽略了 value,它默认使用 None面给个例子,用很 清爽的方式初始化一个,并用它来统计的小写 ASCII 母的出数 import string count by普letter = dict.fromkeys(string.ascii普lowercase, 0) 普 更多资料 Library Reference 和 Python in a Nutshell 中内建的 dict 和 zip,以模块 itertools 和 string 的文档 4.12 将列表元素交地作键和值来建 感谢:Richard Philips、Raymond Hettinger Python 159 任务 给定一个列表,需要交地使用列表中的元素作键和对值来建一个 解方案 内建的 dict 提供了很多建的方法,但是并没有提供种方式,所以们得自 写一个函数来达到目的一个方法是,对扩展的列表调用内建的 zip 函数 def dictFromList(keysAndValues): return dict(zip(keysAndValues[::2], keysAndValues[1::2])) 一个更通用的适合任何序列或者迭代参数的方式是,把给定序列中获多个数对 pair的过程独立出来,一个单独的生器方法如 dictFromList 简洁,但是 度更快,通用性更好 def pairwise(iterable): itnext = iter(iterable).next while True: yield itnext( ), itnext( ) def dictFromSequence(seq): return dict(pairwise(seq)) 定 pairwise 函数使得们以用任意序列来更新任意一个在的,如, mydict.update(pairwise(seq)) 论 本节介绍的种厂函数的方式本质都是用的方法建都生 了一个(key, value)值对的序列,并将作参数传递给 dict别是它们体怎生 个值对的序列 dictFromList 使用内建函数 zip,并用 keysAndValue 列表的个作参数—个 别据奇偶索引搜元素一个的索引是 024…一个是 135… 个方法错,但在 keysAndValues 是支持扩展的类型或者类的例时才有效, 如 listtuple 或者 str外,它会在内中建一些临时列表,如果 keysAndValues 是个很长的序列,些列表的建过程会降一些性能 而 dictFromSequence 把建值对序列的任务委托给了一个做 pairwise 的生器 pairwise 的方式使得它能够使用任何迭代对象—仅仅是列表或者元 符串等,甚包括他生器的结果文等而 pairwise 一生一 对,它来在内中建大列表,因使给定的序列很长,它的性能会有什 降 pairwise 的很有趣pairwise 的第一行语将内建函数 iter 用于传入的 iterable 160 第 4 参数,获得一个迭代器,然再将一个本地量 itnext 绑定到个迭代器的 next 方法 看去有点奇怪,但是 Python 中一种很好的通用的如果你有一个对象, 你想对个对象所做的是在循中断调用它的一个方法,以给它的个被绑定 的方法赋一个本地的,然就以直接调用个本地了,就像调用一个函 数一对某些惯他语言的用户来说,像面调用 next 方法能会更舒适 一些 def pairwise普slow(iterable): it = iter(iterable) while True: yield it.next( ), it.next( ) 过,个 pairwise普slow 体并解方案中的 pairwise 简单对懂 Python 的 人来说显得更熟悉并表示更简单,而它慢了约 60%视简洁和清的确 很要,而是 Python 的心值完全考虑任何性能问题是一种张,但 在践中种点能在任何语言中得到荐所以,既然们都知道编写确 清简单代码的要性,那什学和遵循那些更加符合们需要的 Python 用法呢? 更多资料 19.7 节中的于用滑动窗口来循迭代对象的通用方法参考 Python Reference Manual 中更多于扩展的资料 4.13 获的一个子 感谢:David Benjamin 任务 你有一个大的,中的一些键属于一个定的合,而你想建一个包含 个键合对值的新 解方案 如果你想改动原 def sub普dict(somedict, somekeys, default=None): eturn dict([ (k, somedict.get(k, default)) for k in somekeys ]) r 如果你原中删除那些符合条的条目 def sub普dict普remove(somedict, somekeys, default=None): eturn dict([ (k, somedict.pop(k, default)) for k in somekeys ]) r Python 161 面是个函数的使用和效果 >>> d = {'a': 5, 'b': 6, 'c': 7} >>> print sub普dict(d, 'ab'), d {'a': 5, 'b': 6} {'a': 5, 'b': 6, 'c': 7} >>> print sub普dict普remove(d, 'ab'), d {'a': 5, 'b': 6} {'c': 7} 论 在 Python 中,在很多地方都用到了—数据的行键和复合键,用于模板 解析的量空间等常常需要基于外一个有的大建一个新, 的键是大的键的一个子在大多数情况,原保持但有时, 需要在完了抽之删除在原中的子本节的解方案对种能性都 给出了答案别仅仅在于,如果需要原保持原,使用 get 方法,如果需要 删除子,使用 pop 方法 如果 somekeys 中的某元素 k 并是 somedict 的键,解方案提供的函数会将 k 作结 果的键,并对一个默认值以作一个选的参数传递给个函数,默认情况 是 None所以,结果一定是 somedict 的子过发种行方 式对的用非常有帮助 你认 somekeys 中的所有的元素都是 somedict 的键时,许会希望在键缺失 的时候获得一个常,它以提示和警告你程序中的 bug记,Tim Peters 在 The Zen of Python 中说过错误被静静地略过,除非有意之在 Python 的交互式解 释器的提示符敲入 import this 并回车,你将看到精炼的 Python 计原所以,如 果你的用的角度看,键配是一个错误,那会希望马得到一个常来提醒 你错误的发生如果的确是你所希望的,以对解方案中的函数略作修改 def sub普dict普strict(somedict, somekeys): return dict([ (k, somedict[k]) for k in somekeys ]) def sub普dict普remove普strict(somedict, somekeys): eturn dict([ (k, somedict.pop(k)) for k in somekeys ]) r 些更加的体本甚原本更简单—充说了 Python 本来就喜在 意外发生时抛出常 或者,你希望在键配时直接将忽略需要一点点修改 def sub普dict普select(somedict, somekeys): return dict([ (k, somedict[k]) for k in somekeys if k in somedict]) def sub普dict普remove普select(somedict, somekeys): return dict([ (k, somedict.pop(k)) for k in somekeys if k in somedict]) 列表中的 if 子做完了们期望的,在用 k 之前先做鉴别作 在 Python 2.4 中以用生器表达式来代列表,用它作本节中的函数的参数 162 第 4 们需略微修改 dict 的调用,将 dict([ …])改 dict( … )移除临圆括的方括, 就能享一的简化和度的提升过些修改适用 Python 2.3,因它支持 列表而支持生器表达式 更多资料 Library Reference 和 Python in a Nutshell 文档中于 dict 的部 4.14 反转 感谢:Joel Lawhead、Ian Bollinger、Raymond Hettinger 任务 给定一个,将的键映射到的值而你想建一个反转的,将 各个值反映射到键 解方案 以建一个函数,函数传递一个列表作 dict 的参数以建需要的 def invert普dict(d): eturn dict([ (v, k) for k, v in d.iteritems( ) ]) r 对于较大的,用 Python 准 itertools 模块提供的 izip 会更快一些 from itertools import izip def invert普dict普fast(d): return dict(izip(d.itervalues( ), d.iterkeys( ))) 论 如果 d 中的值是独一无的,那 d 无法被真地反转,就是在的 ,对于任意给定的键 k,满足 id[d[k]]==k过,本节展示的函数在种情况 然能够建一个伪反转 pd,对于任何属于 d 地值 v,d[pd[v]]==v如果 给你原始的 d,以用本节函数获得的 x,以很容易地检查 x 是 d 的反转 是伪反转仅 len(x)==len(d)时,x 才是 d 的真的反转是因, 如果个的键对相的值,对于解方案给出的个函数来说,个键中的一 个一定会失,因而生的伪反转的长度会原的长度短在任何情况, 有 d 中的值是哈希hashable,意味着以用它们做的键的,前面展示的 函数才能常作,,函数会抛出一个 TypeError 常 们编写 Python 程序时,们通常会无视小的优化,如 Donald Knuth 在 30 前所说的度,们更珍视清和确性过,了解更多程序快的知识 Python 163 没有害处们了简单和清而采用某种方法编写程序时,们好深入地考 虑一们的定,要懵懵懂懂 在,解方案中的 invert普dict 函数能会被认更清,因它清楚地表达了它在 做的函数得了由 iteritems 方法生的对的键对值 k 和 v,将它们包裹 (value, key)的序,并把生的序列作参数赋给 dict, dict 就构建出了一 个值键,而原先的键了对值的新—是们需要的反转 而解方案中 invert普dict普fast 函数没有那复,它的操作更加抽象,它首先将 所有的键和值别转个独立的迭代器,再通过调用 Python 准 itertools 模块提 供的 izip 将个迭代器转化一个迭代器,中每个元素都是像(value, key)一的一对 值如果你能够惯于种抽象层,你将体会到更高层的简洁和清 由于种高度的抽象性,以化materialize整个列表而是通过生器和迭代 器一生一的性,invert普dict普fast 能够 invert普dict 快很多如,在的计 算机,反转 10 000 个条目的,invert普dict 耗时 63ms,而 invert普dict普fast 仅用 时 20ms度提升了 3 倍,你处理大规模数据时,由于代码的高度抽象 性而带来的性能提升将会得更加显别是你使用 itertools 来换循和列表 时,执行度能获得极大提升,因你无在内中化一些超大的列表 你惯了更高的抽象层,性能的提升是一个额外收益,除之外,你在念和 性会有所 更多资料 Library Reference 和 Python in a Nutshell 中的映射类型和 itertools 的文档第 19 4.15 的一键多值 感谢:Credit: Michael Chermside 任务 需要一个,能够将每个键映射到多个值 解方案 常情况,是一对一映射的,但要一对多映射难,换说,一个 键对多个值你有个选方案,但体要看你怎看待键的多个对值的复 面种方法,使用 list 作 dict 的值,允许复 d1 = { } d1.setdefault(key, [ ]).append(value) 164 第 4 一种方案,使用子作 dict 的值,自然而然地灭了值复的能 d2 = { } d2.setdefault(key, { })[value] = 1 在 Python 2.4 中,种无复值的方法等地被修改 d3 = { } d3.setdefault(key, set( )).add(value) 论 常的简单地将一个键映射到一个值本节展示了个简单有效的方法来 一个键对多个值的功能,将的值列表或,若在 Python 2.4 中,有 能是合基于列表的方法的语和他者差别大,大的差别是它们对待 值复的态度每种方式都依赖的 setdefault 方法4.10 节有相内容来初始化 的一个键所对的条目,并在需要的时候返回述条目 除了给键增加对值之外,要做更多的情对于使用列表并允许复的第一个方 式,面代码得键对的值列表 list普of普values = d1[key] 如果介意键的所有值都被移除,留一个空列表作 d1 的值,以用面方 法删除键的对值 d1[key].remove(value) 虽然有空列表,但要想检查一个键是少有一个值是很容易的,使用一个总是返 回列表能是空列表的函数就行了 def get普values普if普any(d, key): return d.get(key, [ ]) 如,了检查freep是是 d1 的键somekey的对值之一,以编 写代码if 'freep' in get普values普if普any(d1, 'somekey') 使用子没有值复的第种方式的用法非常类似了获得键的对值列表, 体做法是 list普 f普values = list(d2[key]) o 了移除某键的一个值,以像面做,然,键的值都被删除之, d2 中会留一个空 del d2[key][value] 第种方式,适用于 Python 2.4 以并使用了合的方式,它的移除键值的操作如 d3[key].remove(value) Python 165 第和第种方式无复的 get普value普if普any 函数 def get普values普if普any(d, key): return list(d.get(key, ( ))) 本节论了如何一个很基本的功能,但并没有提到如何以一种系统化的方式来 用它,你许会考虑将些代码封装一个类要达到个目的,得通盘考虑你 的计你能接某个值和某个键的对系出多?用数学的语言表述, 对于每个键而言,条目究是包是合?如果是的, remove 方法究是将总 的对数减一,是完全地删那些对系?是你面临各种各定的一个 开始,过,要想做出确选择,基于用的需来考虑 更多资料 4.10 节Library Reference 和 Python in a Nutshell 于映射类型的节18.8 节中对于 bag 类型的 4.16 用派方法和函数 感谢:Dick Wall 任务 需要据某个制量的值执行的代码段—在他的语言中你能会使用 case 语 解方案 功于面向对象编程的优的派概念,case 语的使用大多但是所有都以 被换他派形式在 Python 中,函数是一等first-class对象个 如函数以作中的值被储,使得case 语的问题更容易被解 如,考虑面的代码段 animals = [ ] number普of普felines = 0 def deal普with普a普cat( ): global number普of普felines print "meow" animals.append('feline') number普of普felines += 1 def deal普with普a普dog( ): print "bark" animals.append('canine') def deal普with普a普bear( ): print "watch out for the *HUG*!" 166 第 4 animals.append('ursine') tokenDict = { "cat": deal普with普a普cat, "dog": deal普with普a普dog, "bear": deal普with普a普bear, } # 模拟,比如,从文件中读取的一些单词 words = ["cat", "bear", "cat", "dog"] for word in words: # 查找每个单词对应的函数调用并调用之 return tokenDict[word]( ) nf = number普of普felines print 'we met %d feline%s' % (nf, 's'[nf==1:]) print 'the animals we met were:', ' '.join(animals) 论 本节的要点是,构建一个,以符串或他对象键,以被绑定的方法函 数或他的调用体作值在每一的执行过程中,们都先用符串键来选择需 要执行的调用体个方法以被做一个通用的 case 语用于各处 确非常简单,经常使用种术以使用被绑定的方法或者他调用 体来换本节示例中查的函数但你使用绑定的方法时,需要传递一个确的 对象作第一个参数来调用它们以将调用体和它所需要的参数放在一个元 中,然把元做的值储来,有更强的通用性 在别的语言中,能需要 caseswitch 或者 select 语,但在 Python 中,所有类似 的地方都用个术来的功能 更多资料 Library Reference 中于映射类型的节Reference Manual 中于绑定和非绑定方法 的介绍Python in a Nutshell 中于和调用体的介绍 4.17 的并交 感谢:Tom Good、Andy McKay、Sami Hangaslammi、Robin Siebler 任务 给定个,需要到个都包含的键交,或者时属于个的键并 解方案 有时,尤是在 Python 2.3 中,你会发对的使用完全是对合的一种体化的 Python 167 体在个要中,需要考虑键,用考虑键的对值,一般以通过调用 dict.fromkeys 来建,像 a = dict.fromkeys(xrange(1000)) b = dict.fromkeys(xrange(500, 1500)) 快计算出并的方法是 union = dict(a, **b) 而快简洁地获得交的方法是 inter = dict.fromkeys([x for x in a if x in b]) 如果 a 和 b 的条目的数目差很大,那在 for 子中用较短的那个,在 if 子中用较长的会有利于提升算度在种考虑之,牲简洁性以获性 能似乎是值得的,交计算以被改 if len(a) < len(b): inter = dict.fromkeys([x for x in a if x not in b]) else: nter = dict.fromkeys([x for x in b if x not in a]) i Python 提供了直接代表合的类型准中的 sets 模块,在 Python 2.4 中经 了内建的部以把面的代码段用在模块的开头,个代码段确保了 set 被绑定到了适合的类型,在整个模块中,无论你用 Python 2.3 是 2.4,都以使 用的代码 try: set except NameError: from sets import Set as set 做的好处是,以到处使用 set 类型,时获得了清和简洁,以度的提升 在 Python 2.4 中 a = set(xrange(1000)) b = set(xrange(500, 1500)) union = a | b inter = a & b 论 虽然 Python 2.3 的准模块 sets 经提供了一个优的数据类型 set 来代表合带 有哈希hashable的元素,但由于历原因,使用 dict 来代表合然是很遍 的基于个目的,本节展示了如何用快的方法来计算种合的交和并本 节的代码在的计算机,并计算耗时 260μs,交计算耗时 690μsPython 2.3 在 Python 2.4 中,个数别是 260μs 和 600μs,而他的基于循或者生器 表达式的方法会更慢 168 第 4 过,好是用 set 类型而是来代表合如本节所示,使用 set 能代码更 加直接和易读如果你喜或操作符|和操作符&,以使用等的 a.union(b) 和 a.intersection(b)操作除了清,度有提升,别是在 Pyton 2.4 中,计算 并需要 260μs,但计算交需要 210μs使是在 Python 2.3,度是以接 的并计算耗时 270μs,交计算耗时 650μs,没有在 Python 2.4 快,但如果你 然用来代表合的,度是相的一点,一你引入 set 类型无论 是 Python 2.4 内建的,是通过 Python 准 sets 模块引入的,接口是一的,你将 获得丰富的合操作个例子,属于 a 或者 b 但属于 a 和 b 的交的合是 a^b, 以等地被表示 a.symmetric普difference(b) 使由于某些原因使用了 dict,能地用 set 来完合操作个例子, 假你有个 phones,将人映射到电码,有个 address,将人映射 到地址清楚简单地打印所有时知道地址和电码的人相数据的方 法是 for name in set(phones) & set(addresses): print name, phones[name], addresses[name] 跟面的方法,非常简洁,虽然清度能有议 for name in phones: if name in addresses: print name, phones[name], addresses[name] 一个很好的选方法是 for name in set(phones).intersection(addresses): print name, phones[name], addresses[name] 如果使用 intersection 方法,而是&交操作,就需要将个都转化 set, 需要中一个然再对转化的 set 调用 intersection,并传入一个 dict 作 intersection 方法的参数 更多资料 Library Reference 和 Python in a Nutshell 的映射类型sets 模块 Python 2.4 中的内建 set 类型 4.18 搜命的子 感谢:Alex Martelli、Doug Hudgeon 任务 你想搜一系列的子,并命些子,而你认用来有点便 Python 169 解方案 任意一个类的例都继了一个被封装到内部的,它用个来记录自的状 态们以很容易地利用个被封装的达到目的,需要写一个内容几乎空 的类 class Bunch(object): def 普 普init普 普(self, **kwds): self.普 普dict普 普.update(kwds) 在,了将量来,建一个 Bunch 例 point = Bunch(datum=y, squared=y*y, coord=x) 在就以问并新绑定那些被建的命属性了,以行添加移除某些 属性之类操作如 if point.squared > threshold: point.isok = True 论 们常常需要搜一些元素,然给它们命个需用来完全没有问题, 但是利用一个几乎什都做的小类显更加方便美 如解方案的代码所示,建一个小小的类,提供参数问的语法,们几乎用 写什东西适合用来搜一些子,每个子都有自的据境, 中的子的键以被认是子的,但如果所有的都是识符,而 被做量使用,并是好的方案在类 Bunch 的普 普init普 普方法中,通过**kwds 语法,以接任意的命参数,并用 kwds 参数来更新例的空,,每个 命的参数都例的一个属性 问属性的语法相,索引语法是那简洁和易读如,如果 point 是个 ,解方案中的的那个代码段就是 if point['squared'] > threshold: point['isok'] = True 外,有一个备选的方案,看去很吸引人 class EvenSimplerBunch(object): def 普 普init普 普(self, **kwds): self.普 普dict普 普 = kwds 将例的新绑定能会人感到安,但调用的 update 方法,它会 什好的果所以,你能会喜个备选的 Bunch 在度的优势 过,来没有在任何 Python 文档中看到对面用法的担保 170 第 4 d = {'foo': 'bar'} x = EvenSimplerBunch(**d) 好使 x.普 普dict普 普 d 的一个独立的拷贝,而是共享一个引用在个方 法的确有效,在各个本中都能作,但除非语法文档规定了语,们能 确信种做法会永有效所以,如果选择了 EvenSimplerBunch 的,你能会 选择赋值一个拷贝dict(kwds)或者 kwds.copy(),而是 kwds 本身而,如果 做,那一点度优势就失了总之,好是将原先的 Bunch 的方法作 首选 一个富诱惑的做法是直接 Bunch 类继 dict,并将属性问的殊方法子 本身的属性方法,像面 class DictBunch(dict): 普 普getattr普 普 = dict.普 普getitem普 普 普 普setattr普 普 = dict.普 普setitem普 普 普delattr普 普 = dict.普 普delitem普 普 普 个方法的一个问题是,据定,DictBunch 的一个例 x 会拥有很多它没有 的属性,因它获得了 dict 所有的属性是方法,但在个境中没什 别所以,你通过 hasattr(x, someattr)来检查属性没有意,但以对先前的 Bunch 和 EvenSimplerBunch 做,而,你得先排除 someattr 的值是一些通用 语如keyspop和get等的能性 Python 的于属性和子的别是门语言清和简洁的源泉幸的是,很多 Python 新手错误地以将属性和子混一谈并没有什问题,大概是因先前的 JavaScript 和他语言的经验,在 JavaScript 中,属性和条目通常是以混一谈的 过新手把概念厘清,要继续稀糊涂 更多资料 Python Tutorial 于类的节Language Reference 和 Python in a Nutshell 中对类的介 绍第 6 于 Python 面向对象编程的介绍4.18 节对于**kwds 语法的介绍 4.19 用一条语完赋值和测试 感谢:Alex Martelli、Martin Miller 任务 你在将 C 或者 Perl 代码转换 Python 代码,并试图量保留原有的结构,你在需 要一种表达方式,能够时完赋值和测试如他语言中的 if((x=foo( ))或 while((x=foo( ))) Python 171 解方案 在 Python 中,能写代码if x=foo(): . . . 赋值是一个语,是一个表达式,而 你能在 if 和 while 中使用表达式作条过问题大,需要将代码修改得更 Python 化一点个例子,要对一个文对象 f 逐行处理,C 风的写法在 Python 中的法是错误的是 while (line=f.readline( )) != '': process(line) 而 Python 风的写法更易读清爽快 for line in f: process(line) 有时,需要将 CPerl 或他语言编写的程序转换 Python 代码,而希望能保 留原有的结构写一个简单的类会到很大的作用 class DataHolder(object): def 普 普init普 普(self, value=None): self.value = value def set(self, value): self.value = value return value def get(self): return self.value # 可选的,强烈不建议使用,但有时确实很方便: import 普 普builtin普 普 普 普builtin普 普.DataHolder = DataHolder 普 普builtin普 普.data = data = DataHolder( ) 在 DataHolder 类和它的例 data 的帮助,原有的 C 风结构得以保留 while data.set(file.readline( )) != '': process(data.get( )) 论 在 Python 中赋值是语,是表达式因,在 ifelif 或 while 语中,你无法 将在测试的东西赋值给他或量没什,调整你的程序结构,避免 在测试的时候赋值代码会显得更清体地说,在任何时候如果你 感到需要在一个 while 循中时完赋值和测试,都认识到,你的循结构 能需要被构一个生器或者他的迭代器一以种思路行构, 你的循就了简单而直接的 for 语解方案给的例子,循读文本文 中的行,是通过 Python 本身完了构,因 file 对象就是一个迭代器,中的 元素是文的行 172 第 4 过,有时转换由 CPerl 或他语言编写的程序到 Python 代码,些语言本身 是支持赋值作表达式的某个经提供了参考的算法或者书的算法等, 并用 Python 编写初本的时候,经常会遇到种用赋值语作表达式的情况在 个条,初的 Python 代码原有的结构是智的以以再对你的 代码构,使之更像 Python—清快等过首先,需要能快地完一个 能作的本,而需要你的代码接原型以便行错误和兼容性的检查幸的是, Python 有足够的能力来满足你的需 Python 们新定赋值的含,过们以写一个方法或函数,把参数 在某处,时能返回那个参数用于测试在,说到某处,们会很自然 地想到用一个对象的属性来代表个某处,因对象的方法是函数更自然的选择 然以直接去问属性,get 方法就得多余了,过,感觉提供 data.set 和 data.get 会更匀整齐一点 data.set(whatever) data.value=whatever 多了一点法的好处,,个加入的值 以是一个表达式因,它是一个很棒的方法,能够帮助们完忠于原型的代 码翻译的 Python 代码和原型代码如 C 或 Perl 代码的唯一别仅仅是法的微 小差—但总体结构一,是们心的议题 引入普 普builtin普 普并给它的属性赋值是一个小花招,本质是在行时定了一个 新的内建对象以在你的用程序开头玩个花招,但马所有的他模块都自 动地有了问个新内建对象的能力,而无引入模块是好的 Python 解问题的方式,相反,是在挑战 Python 的良好品味的尺度,因他模块完全 你的用程序所的副作用负责过,既然本节的目的是提供一个快 肮脏的花招来解代码的初翻译问题,之将要行的构会改良整个代 码,那在殊情况,要们的花招被用于常的产品代码,种手段 以人忍 一方面,有一个花招你是绝对用的,利用列表的一个漏洞 while [line for line in [f.readline( )] if line!='']: process(line) 个花招目前能常作,是因 Python 2.3 和 2.4 都将列表的制量 是 line泄露到周边的空间中是一个很容易混淆和易读的手段,而它 被废了,列表制量的泄露问题将在来的 Python 本中被修,那时个 招数就彻失灵了 更多资料 Tutorial 的于类的文档Library Reference 和 Python in a Nutshell 中于普 普builtin普 普 模块的文档Language Reference 和 Python in a Nutshell 中列表的文档 Python 173 4.20 在 Python 中使用 printf 感谢:Tobias Klausmann、Andrea Cavalcanti 任务 你喜用 C 提供的 printf 函数将某些东西打印到程序的准输出,但 Python 并提供 的函数 解方案 在 Python 中 printf 是很简单的 import sys def printf(format, *args): sys.stdout.write(format % args) 论 Python 开了输出print 语和式化%操作符,如果你希望将个融合在一 ,如解方案所示,它的是很简单的无担心空和新行的自动插入,而 需要担心式和参数的确配问题 个例子,Python 的遍做法是 print 'Result tuple is: %r' % (result普tuple,), 它考虑得非常周到,但个逗的意是很确一个在 result普tuple 之,用于 建单元素元,一个是避免了 print 插入默认的换行符,过既然有了解方案 中提供的 printf 函数,就以写 printf('Result tuple is: %r', result普tuple) 更多资料 Library Reference 和 Python in a Nutshell 中于 sys 模块以符串式化操作符%的文 档2.13 节提出的在 Python 中 C++风的<<的方法 4.21 以指定的概率获元素 感谢:Kevin Parks、Peter Cogolo 任务 你想一个列表中机获元素,就像 random.choice 所做的一,但时据 174 第 4 一个列表指定的各个元素的概率来获元素,而是用等的概率撷元素 解方案 Python 准中的 random 模块提供了生和使用伪机数的能力,但是它并没有提供 殊的功能,所以,们得自写一个函数 import random def random普pick(some普list, probabilities): x = random.uniform(0, 1) cumulative普probability = 0.0 for item, item普probability in zip(some普list, probabilities): cumulative普probability += item普probability if x < cumulative普probability: break return item 论 Python 准中的 random 模块并没有提供据做出选择的功能,种功能在游 模拟和机测试中是很常的需,所以,本节的目是提供功能的解 方案使用了 random 模块的 uniform 函数获得了一个在 0.0 和 1.0 之间的伪机 数,之时循元素概率,计算断增加的累概率,直到个概率值大于伪 机数 本节假但并检查概率序列 probabilities 有和 some普list 一的长度,所有元 素都在 0.0 和 1.0 之间,相加之和 1.0如果反了个假,能行一些机 的撷,但能完全地遵循贯函数的参数所规定的行能想在函数开头 加一些 assert 语以确保参数的有效性 assert len(some普list) == len(probabilities) assert 0 <= min(probabilities) and max(probabilities) <= 1 assert abs(sum(probabilities)-1.0) < 1.0e-5 过,些检查会耗一些时间,所以通常都做,在式的解方案中 没有把它们纳入 如前面提到的,个任务要每一都有一个对的概率,些概率在 0 和 1 之间,总和相加 1一个有点类似的任务是据一个非负整数的序列所定的 行机撷—基于机会,而是概率对于个问题,好的解方案是使用 生器,内部结构和解方案中的 random普pick 函数差很大 import random def random普picks(sequence, relative普odds): table = [ z for x, y in zip(sequence, relative普odds) for z in [x]*y ] while True: yield random.choice(table) Python 175 生器首先准备一个 table,它的元素的数目是 sum(relative普odds)个,sequence 中的每个 元素都以在 table 中出多,出的数等于它在 relative普odds 序列中所对的非负 整数一 table 被制作完,生器的体就以得又小又快,因它需要将机 撷的作委托给 random.choice个例子,于个 random普picks 的型用 >>> x = random普picks('ciao', [1, 1, 3, 2]) >>> for two普chars in zip('boo', x): print ''.join(two普chars), bc oa oa >>> import itertools >>> print ''.join(itertools.islice(x, 8)) icacaoco 更多资料 Library Reference 和 Python in a Nutshell 中的 random 模块 4.22 在表达式中处理常 感谢:Chris Perkins、Gregor Rayman、Scott David Daniels 任务 你想写一个表达式,所以你无法直接用 try/except 语,但你需要处理表达式能抛 出的常 解方案 了抓常,try/except 是少的,但 try/except 是一条语,在表达式内部使用 它的唯一方法是借助一个辅助函数 def throws(t, f, *a, **k): ''' 如果 f(*a, **k)抛出一个异常且其类型是 t 的话则返回 True 或者,如果 t 是一个元组的话,类型是 t 中的某项''' try: f(*a, **k) except t: return True else: return False 个例子,假你有一个文本文,每行有一个数,但文能有多余的内容如 空行注释行等以生一个包含文中的所有数的列表,需略去那些是 数的行 data = [float(line) for line in open(some普file) if not throws(ValueError, float, line)] 176 第 4 论 你能会喜将函数命 raises,但个人更喜 throws,能是出于对 C++的感情 过管什,个辅助函数都接一个常类型 t 作第一个参数,接着是一个 调用体 f,然是任意的基于置的参数 a 和命参数 k,者都将被传递给 f 像 if not throws(ValueError, float(line))的写法是行的你调用函数时,Python 在将制交给函数之前会对参数值,如果参数的值引发了常,函数永会得 到机会执行种情况,在很多人开始用 Python 准的 unittest.TestCase 类的 assertRaises 方法时屡有发生,过一 throws 函数执行时,它在 try/except 语的 try 子中调用 f,将那个任意的基于置 的参数和命参数传递给 f如果在 try 子中对 f 的调用引发了常,常的类型 是 t或者是列出的常类型中的一种,如果 t 是一个常类型的元的,制 交给了对的 except 子,在例中,返回 true 作 throws 的结果如果 try 子 中没有常发生,制会交给对的 else 子如果有,它将返回 false 作 throws 的结果 注意,如果有什非预期的常类型在 t 的指定范围中,throws 函数并会尝试 截获常,因而 throws 就将被,常交给它的调用者是一个有意之的 计在 except 子中用大的网去捕获常并是什好意,那常常意味着查错查 得头脑胀如果调用者真的想要 throws 截获所有的常,它以调用 throws(Exception, … 然,就等着头疼 throws 函数的问题是,做了键操作一是看它有没有抛出常,先把 结果抛脑,一是获得结果所以好的结局是时获得结果和被截获常的 提示开始是做的 def throws(t, f, *a, **k): " 如果 f(*a, **k)抛出异常且异常类型为 t,返回(True, None) 或者(False, x),其中 x 是 f(*a, **k)的结果 " try: return False, f(*a, **k) except t: return True, None 幸的是,个本符合列表的要,没有什优的办法能够时得到志 和结果因,选择了一个的方法一个在任何情况都返回 list 的函数—如 果有常被捕获就返回空列表,就返回仅包含结果的列表个方法作得很好, 但是了清,好把函数改一改 def returns(t, f, *a, **k): " 正常情况下返回 [f(*a, **k)],若有异常返回 [ ] " try: Python 177 return [ f(*a, **k) ] except t: return [ ] 生的列表得更加优,解方案中的本好多了,少认 data = [ x for line in open(some普file) for x in returns(ValueError, float, line) ] 更多资料 Python in a Nutshell 中于截获和处理常的文档4.8 节对*args 和**kwds 语法的介绍 4.23 确保经在给定模块中被定 感谢:Steven Cummings 任务 你想确保某个经在一个给定的模块中定过了如,你想确认经在一个 内建的 set 了,如果被定,你想执行一些代码来完定 解方案 个任务的解办法是到过的 exec 语好的用之地exec 使得们以执行 一个符串中的任意 Python 代码,们以写一个很简单的函数来达到目的 import 普 普builtin普 普 def ensureDefined(name, defining普code, target=普 普builtin普 普): if not hasattr(target, name): d = { } exec defining普code in d assert name in d, 'Code %r did not set name %r' % ( defining普code, name) setattr(target, name, d[name]) 论 如果你的代码要支持很多本的 Python或者支持第方的包,那你的很多模块 能得以的代码段作开头以确保 set 在 Python 2.4 或者 2.3 中都被确 地置了,然在 Python 2.4 中 set 是内建类型,而在 Python 2.3 中们需要 准中入 set try: set except NameError: from sets import Set as set 178 第 4 本节解方案将逻辑直接封装来,默认情况作于普 普builtin普 普模块,是因 你在老的 Python 本中处理问题时,它是型的需要用到的模块用本节的 解方案,需在程序初始化的时候行以代码一,就以确保 set 被确地 定了 ensureDefined('set', 'from sets import Set as set') 个方法的大优势是,你需在初始化的时候,在程序中的某处调用 ensureDefined ,而无在各个模块的开头写一堆 try/except 语外,ensureDefined 使得代码读 性更好,因它做一,因调用它的目的一目了然,而 try/except 语用面 很广,需要花些时间才能看清楚和理解它们一点,try/except 在类似于 pychecker 的检查中能引发警告,而本节的做法以绕过个问题如果你没有用过 pychecker 或者他类似的,紧去试试http://pychecker.sourceforge.net/ 们使用了一个辅助性的 d 作 exec 语的目,而转换被要的, 们竭力避免对 target 的一些预期外的副作用以使用一些非模块的对象 类,甚类的例来作 target,就无给 target 加一个做普 普builtins普 普的 属性来引用 Python 内建类型的如果想用得更意一些,if 语的体部 以 exec defining普code in vars(target) 你将避免地一些副影响, http://www.python.org/doc/current/ref/exec.html 的 介绍 很要的一点是,一定要清楚 exec 能够执行给它的任何包含 Python 代码的有效的符 串因,确保在你调用函数 ensureDefined 时,传递给参数 defining普code 的值 是来自于一个信的来源,如一个被恶意修改过的文本本 更多资料 Python Language Reference Manual 中于 exec 的在线文档http://www.python.org/doc/ current/ref/exec.html Python 179 第 5 搜索和排序 引言 感谢Tim Peters,PythonLabs 在 1960 年代,计算机制造商们曾经估计,如果将所有的用户计入,他们制造的计算机 有 25%的运行时间被用于排序。实际上,有很多计算机花了超过一半的计算时间在排 序上。通过这样的评估结果,我们可以得出结论,可能 i确实有很多非常重要的和 排序相关的应用,或者 ii很多人在进行一些不必要的排序计算,再或者 iii低效 的排序算法被广泛应用造成了计算时间的浪费。 —Donald Knuth The Art of Computer Programming,vol.3,Sorting and Searching,第 3 页 在 Knuth 教的著中,有关搜索和排序题的部是长达 800 页的复杂的术文 献在 Python 实践中,们把它纳要的条们经读过了 Knuth 的书, 所用去读了 • 需要排序的时候,量法使用内建 Python 列表的 sort 方法 • 需要搜索的时候,量法使用内建的 本的很多内容将会展示条原常的题是使用 decorate-sort-undecorate DSU模式,是一种通用的方法,通过建一个辅助的列表,们将题转化 列表的排序,而利用默认的快的 sort 方法个术是在本中有用的 部实,DSU 是如常用, Python 2.4 入了新的特性来使之更易使 用因很多节的解决方案在 Python 2.4 中得简多了,但本也论了本中 经更新过的解决方案 DSU 依赖 Python 的内建较built-in comparison的一个常的特性序列是按照 条目的序lexicographically行较的条目序lexicographical order是对列 180 国国 表和元的符串较母序规的纳假 s1 和 s2 是序列,内建函 数 cmp (s1, s2)等面的 Python 码 def lexcmp(s1, s2): # 找到最靠左的不相等的一对 i = 0 while i < len(s1) and i < len(s2): outcome = cmp(s1[i], s2[i]) if outcome: return outcome i += 1 # 全部相等,其中一个序列已经消耗完所有元素 return cmp(len(s1), len(s2)) 段码试到第一个相等的对元素如果相等的一对元素被到,通过 对元素计算结果或者,如果一个序列好是一个序列的前半截,那较短的 那个序列被认是较小的序列,如果述情况都没有发生,个序列完全 一样,被认相等面是一些例子 >>> cmp((1, 2, 3), (1, 2, 3)) # 相等 0 >>> cmp((1, 2, 3), (1, 2)) # 第一个大,因为第二个是第一个的前一部分 1 >>> cmp((1, 100), (2, 1)) # 第一个小,因为 1<2 -1 >>> cmp((1, 2), (1, 3)) # 第一个小,因为 1==1,然后 2<3 -1 如果想根据键键对一个对象列表行排序,简地建一个元的列 表,中每个元都遵循相的序来储键键对象本身,样们 就基条目序行较由元是按照序较的,所较操作理 地得到确的结果在较元时,键被首先较,仅键相等时, 键才会继续行较 本的 DSU 模式的很多例子展示了种思想在种需求中的用DSU 术 用任意数目的键要愿意,给元增加足够多的键,然它们的序得 按照你希望行的较序行排列在 Python 2.4 中,用 sort 的新的 key= 参数也得到样效果,有几节内容将会展示一用法相手工构建一个辅 助的元列表,使用 sort 方法的 key=参数更加容易更节省内,而度也 更快 Python 2.4 排序供了他改,包括一个方便的快捷方法内建函数 sorted 将任何迭对象排序,并改动原对象,而是首先将复制到一个新的列表Python 2.3没有那个新的的关键参数,该参数既用内建的 sorted 函数也用 list.sort,编写如码来实相的功能 搜索和排序 181 def sorted_2_3(iterable): alist = list(iterable) alist.sort( ) return alist 由列表复制和列表排序都是很轻的操作,而内建的 sorted 需要执行些操作, 所使用内建的 sorted 函数会获得度的势,它的势就在方便有个预先 实的函数在手边,总每都要写四行程序来完样功能人心情愉快一些, 是个实用的题一方面,一些小函数用得非常广泛和频繁,对原有的内建对 象和函数行扩展得非常必要Python 2.4 增加的 sorted 和 reversed 函数在之前经 被要求了很多 自本书第一出之,Python 的排序的大的化是 Python 2.3 采用了新的排序实 由产生的明显别是,很多常用操作的度快了,而新的排序是稳定排序 如果原列表中的个被较的元素相等,那在完排序之它们的相对序 个新的实是如功,一高的空间似乎都大了,Guido 也被 说服并宣布 Python 的 list.sort 方法将永是稳定排序个始 Python 2.4 的保证实 经在 Python 2.3 中实了然,排序的发展历断地醒们,更好的方法也许 没被发因,们也会概要绍一 Python 的排序的发展历 Python 排序的简短历 在早期的 Python 发行中,list.sort 使用 C 供的 qsort 例程个排序方法 被换的原因有好几个,但要是由 qsort 的质量在的计算机差很大在 对一个带有很多相等的值或者完全反序的列表的排序中,一些本慢得人无法容忍 一些本甚会引发 Core Dump,因它们是入的用户定的_ _cmp_ _函数 也能会调用 list.sort,所一个 list.sort 会调用他的 list.sort,是较操作的副作用 一些的 qsort 例程无法处理种状况一个用户定的_ _cmp_ _函数也能假如 它是恶意的或者疯了在在排序的时候修改列表,很多的 qsort 例程在种情况 发生时都会引发 Core Dump Python 因而发展了自的快排序算法每个发行都会写算法,因总是能在 实际用中发慢得无法接的情况快排序真的是一种脆弱而精的算法 在 Python 1.5.2 中,快排序算法被换了抽样排序和折半插入排序的混合体,个 算法超过四保持,直到 Python 2.3 世抽样排序被看做是快排序的种, 它使用很大的样本空间来挑划元素partitioning element,也被轴心它对元 素的一个大的随机子递地抽样排序并挑它们的中值个种使得方时间 复杂度的行得几乎能,时也均的较数非常接理论的小值 过,抽样排序是一个复杂的算法,对小列表而言,它有较大的管理的开销 因,小列表抽样排序划出来的小结果由独立的折半插入排序算法处 182 第 5 理—实就是一种普通的插入排序,过它用搜索来决定新元素的属很 多关排序的文说值得烦恼,因他们假较个元素的开销要在内中 交换元素的开销小,但对 Python 的排序来说,个假立移动一个对象非常 容易,因复制的过是一个对象的引用较个对象的开销较昂贵,是 因要通过面向对象机制合适的码来较个对象,时部码每都被 强制调用因一点,搜索在 Python 的排序中是很功的用 基个混合方法,一些特殊情况被特别照顾利首先,经排序或者反序 的列表会被检查出来,并线性的时间复杂度来处理对一些用而言,种类型 的列表很常,如果一个序列经大部完排序,有少数的元素 然处乱序状,排序工作将由折半插入排序算法接手完全交由抽样排序算 法处理要快一些,尤是一些特殊的用需要断地将列表排序,加入一个新元素, 然再排序然,抽样排序算法中的一些特别的码会检查相等相邻一段元素, 并将些段记完部 ,所有些努力产生了一个极的排序算法,无论是在实际的使用中体出 来的高效,是针对一些常的特殊例子所展的梦幻般的度,都充证明了算 法的功个算法包括了大 500 行非常复杂的 C 码,和 5.11 节展示的例子 说有壤之别 抽样排序算法经行之有,也说过要请能够写出更快的 Python 排序的人饭 过,是能一个人去饭也然在继续关注一些文献资料,因 混合抽样排序算法的一些题然耿耿 • 虽然没有在实际中发方时间复杂度行的例子,但知道样的例子 一定是计出来的,因要计一个均度慢到倍的用例是非常 容易的 • 针对某些极端偏序partial order的特例的化在实际用中非常有用,但真实数 据中经常出他类型的偏序,些类型的偏序也被特别处理实, 经开始相信真的随机输入序在实生活中根本能在然要把用测试 排序算法的时间复杂度的例子排除 • 如果增加内的使用量,在没有什行的方法来使抽样排序稳定 排序 • 了对一些特例行化而使码得极复杂晦涩丑陋 前的排序 很明显并排序有几个点,它的结果保证 nlogn 时间复杂度,而很容易实 稳定排序题是在 Python 中的多对并算法的实产生的是更慢的结果 搜索和排序 183 抽样排序,并排序过多地移动了数据和更高的内耗 很多文献资料—而是越来越多的,开始关注适性排序算法adaptive sorting algorithm,种算法试探测的输入中的元素序写了很多种算法的实, 但它们都 Python 的抽样排序慢得多,除非例子是被门计的些算法的理论基 础较复杂,因难产生有效的行算法来读到了一篇文,该文指出列表 的合并自然地揭示了很多类型的偏序,需简地注意一每个输入列表连续赢 的频率个信息很简但也很有普遍性意识到将它用在一个自然的 并排序中,而种方法能够很好地对所知道和关心的种特例时,就痴迷 地投入到了高随机数据处理的度减轻内负的工作中 ,Python 2.3 中的适性强的自然的稳定的并排序了一个很大 的功,但时也是一个工程的硬骨头—魔鬼藏在节之中该算法的实包 括了大 1 200 行 C 程序,但像混合抽样排序的码,些码没有一行是特 例准备的,而大概有一半是在实一个术的,使得情况的内 负减轻一半对个算法感到骄傲,但是引言部经没有多篇幅供 解释节了如果你很好奇,写了一个很长的术述文档,在 Python 的源码发行包中到目录中—也就是你解 Python 源码发行包的地方— 如,Python-2.3.5 或者 Python-2.4的 Objects/listsort.txt在面的列表中, 供了 Python 2.3 的并排序利用的偏序的例子,排序完意味着向或 者反向序 • 输入序列经是排序完状 • 输入序列接排序完状,但是有一些随机元素处部或中间,或者处 都有 • 输入序列是个或多个排序完列表的拼接实,在 Python 中快的并多 个排序完的列表的方法就是先将它们连接来,然执行 list.sort • 输入序列有多个键对相的值如,对股票交易所的数据中的美公排序, 些公大多和 NYSE 或者 NASDAQ 联系来算法能够利用特例的原因是 根据对稳定的定,拥有相的键的记录经是排序完状!算法能够自然 地探测到一点,无特意相等键的码 • 输入序列是排序完状,但是小心在地摔很多块了每一块都在 随机的置,而中的某些块内部也经新洗牌了虽然看去是挺傻 的例子,但它然能够被算法利用并升处理性能,由种方法的通 用性 长话短说,Python 2.3 的姆排序timsort,嗯,它得有个短点的是稳定 的健壮的,而在实际用中也快得像飞一样,能的择用它! 184 第 5 5.1 对排序 感谢Alex Martelli 任务 你想对排序能意味着需要先根据的键排序,然再对值也处 样的序 解决方案 简的方法通过样的述来概括先将键排序,然由出对值 def sortedDictValues(adict): keys = adict.keys( ) keys.sort( ) return [adict[key] for key in keys] 论 排序的概念仅仅适用那些有序的—换话说,一个序列而一个映射,如 ,是没有序的,因它无法被排序然而,怎才能将一个排序是 Python 邮列表中一个很常的题,理论个题没有什意在绝大多数情 况,实际目的是将中的键构的序列排序 实部,一些人总是考虑更复杂的方式,但实解决方案中给出的简的方 法也是快的方法对 Python 来说,样的情况并少在 Python 2.3 中,在函数 的的 return 语中,将列表推转换对 map 的调用获得一些度的升, 大 20%如 return map(adict.get, keys) 解决方案中的码在 Python 2.4 经 Python 2.3 要快了,按照面的方式行改写 也会获得很大的度升而使用他方法,如用 adict._ _getitem_ _来 adict.get,并会供任何性能的升,反而会引性能的些微降,无论是在 Python 2.3 是 2.4 中 更多资料 5.4 节的方案,根据的对值而是键行排序 5.2 大小写对符串列表排序 感谢Kevin Altis、Robin Thomas、Guido van Rossum、Martin V. Lewis、Dave Cross 搜索和排序 185 任务 你想对一个符串列表排序,并忽略大小写信息举个例子,你想要小写的 a 排在 大写的 B 前面默认的情况,符串较是大小写敏感的如所有的大写符排 在小写符之前 解决方案 采用 decorate-sort-undecorateDSU用法既快简 def case_insensitive_sort(string_list): auxiliary_list = [(x.lower( ), x) for x in string_list] # decorate auxiliary_list.sort( ) # sort r turn [x[1] for x in auxiliary_list] # undecorate e Python 2.4 经供对 DSU 的原生支持了,因假 string_list 的元素都是真的普 通符串,而是 Unicode 对象之类,用更简短更快的方式 def case_insensitive_sort(string_list): return sorted(string_list, key=str.lower) 论 一个很明显的方案是编写一个较函数,并将传递给 sort 方法 def case_insensitive_sort_1(string_list): def compare(a, b): return cmp(a.lower( ), b.lower( )) string_list.sort(compare) 过,在每较中,lower 方法都会被调用,而对长度 n 的列表来说,较 的数 nlog(n) DSU 方法建了一个辅助的列表,每个元素都是元,元的元素来自原列表并 被做键处理个排序是基键的,因 Python 的元的较是根据条目序 行的如,它会首先较元的第一个元素要将一个长度 n 的符串列表排 序,配合 DSU 的使用,lower 方法需要被调用 n ,因而在第一,decorate 阶段, 一,undecorate 阶段节省了很多时间 DSU 有时也被—但种法是很准确—Schwartzian 换,是对 Perl 的一 个著用的一个准确的类如果要说相似,DSU 更接 Guttman-Rosler 换, http://www.sysarch.com/perl/sort_paper.html DSU 是如要,因 Python 2.4 供了对它的直接支持给列表的 sort 方法传 递一个的命参数 key,而它被调用,作用列表中的每个元素并获得用 排序的键如果传递样的一个参数,排序会在内部使用 DSU因,在 Python 2.4 中, string_list.sort(key = str.lower 实际等 case_insensitive_sort 函数,过 sort 方法 186 第 5 会直接作用原列表返回 None,而是返回一个排序完的拷贝对原列表做 任何修改如果你希望 case_insensitive_sort 函数也能够直接作用原列表,需要将 return 语修改对列表本体的赋值 strin _list[:] = [x[1] for x in auxiliary_list] g 反过来,在 Python 2.4 中,如果你希望获得一个排序完的拷贝,原列表保持, 使用新的内建的 sorted 函数如,在 Python 2.4 中 for s in sorted(string_list, key=str.lower): print s 述码打印列表中的每一个符串,些符串根据大小写无关的规行排序, 而会影响到 string_list 本身 在 Python 2.4 的解决方案中,将 str.lower 作 key 参数限制了你特定的方式将符串 排序包括 Unicode 对象如果你知道你在排序的是 Unicode 对象列表,使 用 key = unicode.lower如果你希望函数能够时适用普通符串和 Unicode 对象, import string 并使用 key = string.lower外,也使用 key = lambda s: s.lower( ) 如果需要对符串列表行大小写无关的排序,能也需要用大小写无关的符串作键的 和合,需要列表对 index 和 count 方法表出大小写无关的行方式,需要在种 搜索配的任务中忽略大小写,等等如果是你的需求,那真需要的是 str 的一个 子类型,而在较和哈希的时候忽略大小写—相实种容器和函数来满足些需 求,是解决类题的好的方法参考 1.24 节内容,看到如何实样一种子类型 更多资料 Python Frequently Asked Questions,http://www.python.org/cgi-bin/faqw.py?req=show&file= faq04.051.htp5.3 节Python 2.4 的 Library Reference 中关 sorted 内建函数,sort 和 sorted 的 key 参数1.24 节 5.3 根据对象的属性将对象列表排序 感谢Yakov Markovitch、Nick Perkins 任务 需要根据个对象的某个属性来完对整个对象列表的排序 解决方案 DSU 方法然一如既往地有效 def sort_by_attr(seq, attr): intermed = [ (getattr(x, attr), i, x) for i, x in enumerate(seq) ] 搜索和排序 187 intermed.sort( ) return [ x[-1] for x in intermed ] def sort_by_attr_inplace(lst, attr): lst[:] = sort_by_attr(lst, attr) 由 Python 2.4 的对 DSU 的原生支持,码写得更短跑得更快 import operator def sort_by_attr(seq, attr): return sorted(seq, key=operator.attrgetter(attr)) def sort_by_attr_inplace(lst, attr): lst.sort(key=operator.attrgetter(attr)) 论 根据对象属性将对象排序的佳方法然是 DSU,如前面 5.2 节所绍的在 Python 2.3 和 2.4 中,DSU 再像过去那样,是用来确保排序的稳定性的方法了因 Python 2.3 开始,排序将一直保持稳定,但 DSU 的度势然如故 排序,针对普遍的用例,采用好的算法,时间复杂度是 O(nlogn)如常的数 学公式一样, n 和 logn 之间是相乘的关系通过使用 Python 原生的较操作也 是快的,DSU 的度大多来自对 O(nlogn)部的加,O(nlogn)决定着长度 n 的序列的排序时间在预备的 decoration 阶段,准备辅助的元列表阶段,功 的 undecoration 阶段,在完排序的中间结果的列表的元中出需要的元素的 阶段,时间复杂度仅仅是 O(n)因,如果 n 非常大,个阶段的一些效的操作 并会很大的影响,在实际用中,它们也确实影响甚微 O( )记法 当我们需要思考性能问题时,采用的最有效的方法就是众所周知的大 O 分析法以及 记法O 表示的是“order”。可以在 http://en.wikipedia.org/wiki/Big_O_notation 看到 非常详细的解释,但这里我们只是给出了一个概要。 如果我们考虑对尺度为 N 的输入数据采用某个算法,则运行时间是可以描述的,例 如针对足够大的 N具有很大输入数据的应用通常会最关心性能问题 ,时间和 N 的 函数成正比。对于这种表述,我们记录为相应的符号 O(N)运行时间正比于 N处 理 2 倍的数据需要 2 倍的时间, 10 倍的数据则需要 10 倍的时间,以此类推也被称 为线性时间复杂度, O(N squared)运行时间正比于 N 的平方处理 2 倍的数据, 需要花费 4 倍的时间,10 倍的数据,则需要 100 倍的时间这也被称为二次方时间 复杂度,等等。另一个常见的情况是 O(NlogN),它比 O(N squared)快但比 O(N)慢。 常数比率通常是被忽略的至少在理论分析中是这样,因为它常常依赖于一些非算 法的因素,如计算机的时钟频率,而不是算法本身。比如你买了一台计算机,它比你 的老计算机快两倍,所有的事情处理起来都只需要一半时间,但这并不会改变不同算 法之间的差异。 188 第 5 本节的方案是,给 intermed 列表的每一个元素所在的元中加入了一个索引 i,对 的 x 之前x 是 seq 的第 i 个元素个举措保证了 seq 中任意个子都会被直 接用较,使对一个属性 attr 它们都有相的值在种情况,它们的索 引然会保持,因基 Python 的根据条目序较lexicographical comparison 的规,元的一个元素 seq 的元素无被用较避免对象的较将极 大地高性能举个例子,们根据 real 属性对一个复数列表行排序如果直 接较个复数,们会引发一个常,因复数之间并没有定序但是如 们前面到的,样的情况永会发生,因排序将会确地行去 5.2 节经到过,Python 2.4 直接支持 DSU传递一个的关键参数 key 给 sort,样每个元素都用它来获排序的键准模块 operator 有个新函数, attrgetter 和 itermgetter,它们被用来返回适用的调用体在 Python 2.4 中,针对个 题的解决方案就了 import operator seq.sort(key=operator.attrgetter(attr)) 个段执行的排序是直接用原列表的,因度快得惊人—在的计算机, 解决方案给出的第一个 Python 2.3 的函数快 3 倍如果需要的是一个排序的拷贝, 而想影响 seq,使用 Python 2.4 新的内建的 sorted 函数 sorte _copy = sorted(seq, key=operator.attrgetter(attr)) d 过它没有直接用原列表的排序快,个码段解决方案的第一个函数快 2.5 倍外,Python 2.4 保证了,如果传入了的 key 命参数,列表的元素永会 被直接较,因无他的安全保障而,排序的稳定性也是有保证的 更多资料 5.2 节Python 2.4 的 Library Reference 文档中有关 sorted 内建函数,operator 模块的 attrgetter 和 itermgetter 函数, sort 和 sorted 的 key 参数 5.4 根据对值将键或索引排序 感谢John Jensen、Fred Bremmer、Nick Coghlan 任务 需要统计元素出的数,并根据它们的出数安排它们的序—如, 你想制作一个柱状 解决方案 柱状,如果考虑它在形像的含,实际是基种元素用 Python 搜索和排序 189 的列表或很容易处理出的数,根据对值将键或索引排序面是 dict 的 一个子类,它了种用加入了个方法 class hist(dict): def add(self, item, increment=1): ''' 为 item 的条目增加计数 ''' self[item] = increment + self.get(item, 0) def counts(self, reverse=False): ''' 返回根据对应值排序的键的列表 ''' aux = [ (self[k], k) for k in self ] aux.sort( ) if reverse: aux.reverse( ) return [k for v, k in aux] 如果想将元素的统计结果放到一个列表中,做法也非常类似 class hist1(list): def _ _init_ _(self, n): ''' 初始化列表,统计 n 个不同项的出现 ''' list._ _init_ _(self, n*[0]) def add(self, item, increment=1): ''' 为 item 的条目增加计数 ''' self[item] += increment def counts(self, reverse=False): ''' 返回根据对应值排序的索引的列表 ''' aux = [ (v, k) for k, v in enumerate(self) ] aux.sort( ) if reverse: aux.reverse( ) return [k for v, k in aux] 论 hist 的 add 方法展示了 Python 用统计任意哈希的元素的常用方法,并使用 dict 来记录数在类 hist1 中,在一个普通的列表的基础,们采用了的方法,并 在_ _init_ _中将所有的数都置 0,因而 add 方法就得更简了 counts 方法生了一个键或者索引的列表,并根据对值行了排序个类针对 的题很类似,因解决方式也几乎完全一样,都使用了前面 5.2 节和 5.3 节展示过的 DSU如果们想要在自的程序中使用个类,由它们的相似性,们该 行码构,中间离出共性并置入一个独的辅助函数_sorted_keys def _sorted_keys(container, keys, reverse): ''' 返回 keys 的列表,根据 container 中的对应值排序 ''' aux = [ (container[k], k) for k in keys ] aux.sort( ) if reverse: aux.reverse( ) return [k for v, k in aux] 然实个类的 counts 方法,实就是对_sorted_keys 函数行一层很薄的封装 190 第 5 class hist(dict): ... def counts(self, reverse=False): return _sorted_keys(self, self, reverse) class hist1(list): ... def counts(self, reverse=False): return _sorted_keys(self, xrange(len(self)), reverse) DSU 在 Python 2.4 中非常要,前面 5.2 节和 5.3 节经绍过了,列表的 sort 方法和 新的内建的 sorted 函数供了一个快的原生的 DSU 实因,在 Python 2.4 中, _sorted_keys 得更简快 def _sorted_keys(container, keys, reverse): return sorted(keys, key=container._ _getitem_ _, reverse=reverse) 被绑定的 container._ _getitem_ _方法和 Python 2.3 中实的获索引的操作 container[k] 所做的情完全一样,但是对们在排序的序列而言,它是一个调用体, 用序列中的每个元素,命的键,因们将它传递给内建 sorted 函数的 作 key 关键参数的值Python 2.4 供了一个简直接的方法来获元素根 据值排序的列表 from operator import itemgetter def dict_items_sorted_by_value(d, reverse=False): return sorted(d.iteritems( ), key=itemgetter(1), reverse=reverse) 如果想排序一个元素子容器的容器,Python 2.4 新出的高函数 operator.itermgetter 是一个很方便的供 key 参数的方法,它针对每个子容器的特定元素建立键 是们想要的,因的条目实际就是一个键和值构的对元素的元 的序列,所谓根据对值排序,就是根据每个元的第个元素行排序 回到本节的题,面是本节解决方案中的 hist 类的一个使用示例 sentence = ''' Hello there this is a test. Hello there this was a test, but now it is not. ''' words = sentence.split( ) c = hist( ) for word in words: c.add(word) print "Ascending count:" print c.counts( ) print "Descending count:" print c.counts(reverse=True) 述码段产生了如的输出 Ascending count: [(1, 'but'), (1, 'it'), (1, 'not.'), (1, 'now'), (1, 'test,'), (1, 'test.'), (1, 'was'), (2, 'Hello'), (2, 'a'), (2, 'is'), (2, 'there'), (2, 'this')] Descending count: 搜索和排序 191 [(2, 'this'), (2, 'there'), (2, 'is'), (2, 'a'), (2, 'Hello'), (1, 'was'), (1, 'test.'), (1, 'test,'), (1, 'now'), (1, 'not.'), (1, 'it'), (1, 'but')] 更多资料 Language Reference 的特殊方法一节 Python in a Nutshell 中 OOP 节,特 殊的_ _getitem_ _方法Library Reference 中关 Python 2.4 的内建 sorted 函数 sort 和 sorted 的 key=参数 5.5 根据内嵌的数将符串排序 感谢Sébastien Keim、Chui Tey、Alex Martelli 任务 你想将一个符串列表行排序,些符串都含有数的子串如一系列邮寄地 址举个例子,foo2.txt该出在foo10.txt之前然而,Python 默认的符串 较是基母序的,所默认情况,foo10.txt会在foo2.txt之前 解决方案 需要先将每个符串割开,形数和非数的序列,然将将每个序列中的数 转化一个数会产生一个数的列表,用来做排序时较的键,对个排 序用 DSU—写个函数,做来很快捷 import re re_digits = re.compile(r'(\d+)') def embedded_numbers(s): pieces = re_digits.split(s) # 切成数字与非数字 pieces[1::2] = map(int, pieces[1::2]) # 将数字部分转成整数 return pieces def sort_strings_with_embedded_numbers(alist): aux = [ (embedded_numbers(s), s) for s in alist ] aux.sort( ) return [ s for _ _, s in aux ] # 惯例_ _ 意味着“忽略” 在 Python 2.4 中,用相的 embedded_number 函数,加 DSU 的原生支持,码 def sort_strings_with_embedded_numbers(alist): return sorted(alist, key=embedded_numbers) 论 假有一个排序的文的列表,如 files = 'file3.txt file11.txt file7.txt file4.txt file15.txt'.split( ) 192 第 5 如果是排序并打印列表,如在 Python 2.4 中用 print ' '.join(sorted(files))样的码, 你的输出会是样file11.txt file15.txt file3.txt file4.txt file7.txt,因默认情况, 符串是根据母序排序的或者换话说,排序序是由条目序指定的Python 猜到你的真实意实是希望它外的方式处理那些含有数的子串,所必 准确地告诉 Python 你想要什,解决方案中的码要所做的工作实就是 一 基解决方案的码,也能获得一个更好看的结果 print ' '.join(sort_strings_with_embedded_numbers(files)) 在输出了 file3.txt file4.txt file7.txt file11.txt file15.txt,该好就是需要的 序 个实基 DSU如果想在 Python 2.3 中达到样目的,需要手工制作 DSU,但如 果你的码需要在 Python 2.4 中行,直接使用原生内建的 DSU 们传递了 一个做 key 的参数一个函数,该函数对每个元素都会调用一获确的较 键来用排序给新的内建函数 sorted 本节解决方案中的 embedded_numbers 函数是用来每个元素获确的较键的方 法一个非数子串交出的列表,int 获了每个数子串re_digits.split(s)给了 们一个交出的数子串和非数子串的列表数子串拥有数索引,然 们使用了内建的 map 和 int采用了扩展的方式获得并置了数索引的元素 来将数序列转化整数在,对个混合类型的列表行的条目序较就 产生确的结果了 更多资料 Library Reference 和 Python in a Nutshell 文档中关扩展 re 模块部Python 2.4 Library Reference 中的内建函数 sorted sort 和 sorted 的 key 参数5.3 节和 5.2 节 5.6 随机序处理列表的元素 感谢Iuri Wickert、Duncan Grisby、T. Warner、Steve Holden、Alex Martelli 任务 你想随机的序处理一个很长的列表 解决方案 一如既往的,在 Python 中简的方法常常是好的如果们允许修改输入列表中 搜索和排序 193 的元素的序,那面的函数就是简和快的 def process_all_in_random_order(data, process): # 首先,将整个列表置于随机顺序 random.shuffle(data) # 然后,根据正常顺序访问 for elem in data: process(elem) 如果们需要保证输入列表,或者输入列表能是他迭对象而是列表, 在函数体开头加一条赋值语 data = list(data) 论 虽然过度关心度常常是个错误,但们也能忽略算法的性能假们必 随机序处理一个复的长列表的元素第一个想法能会是样们反 复地随机地挑出元素通过 random.choice 函数,并将原列表中被挑的元素删除, 避免复挑 import random def process_random_removing(data, process): while data: elem = random.choice(data) data.remove(elem) process(elem) 然而,个函数慢得怕,使输入列表有几个元素每个 data.remove 调用都 会线性地搜索整个列表获要删除的元素由第 n 的时间耗是 O(n),因 整个处理过程的耗时间是 O(n2),列表长度的方而要乘一个很大 的常数 对第一个想法的一点高是将注意力中在获随机索引,并使用列表的 pop 方法 来时获和删除元素,种更层的方式避免了较大的耗,尤是在某些情况, 如要被挑的元素列表的,或者使用的根是列表,而是或合 若们面对的是或合,一条思路是寄希望使用 dict 的 popitem 方法或者 sets.Set 或 Python 2.4 内建类型 set 的等的 pop 方法,看去个函数好像被计 随机择一个元素并删除之,但是,小心 dict.popitem 的文档指出,它返回并删除中的任意一个元素,但和真的随机元 素差得很看看个 >>> d=dict(enumerate('ciao')) >>> while d: print d.popitem( ) 你能会很惊,在大多数的 Python 实中,个码段都将看去随机的 方式打印 d 的元素,通常是(0, 'c') ,然(1, 'i'),等等一话,如果需要 Python 中的 伪随机行,需要的是准的 randompopitem 模块 194 第 5 如果你考虑使用而是列表,那你肯定在Python 式思维的路前了一 ,虽然并会针对个特定题供什性能势但相择确的数据 结构,更有 Python 风格的方式是总是利用准Python 准是个庞大丰富 的,塞满了种有用的强健的快的函数和类,满足种用的需求在 个前,关键的一点是要意识到,想要随机的序序列,简的方法是 首先将序列转化随机的序也被对序列洗牌,是对扑克洗牌的类,然再 线性的洗完牌的序列random.shuffle 函数就执行洗牌操作,本节解决方 案是利用了个函数 实际性能总是需要测试,而是猜测出来的,那也是准模块 timeit 在的原因 使用一个空的 process 函数和一个长度 1 000 的列表作 data,process_all_in_random_ order 能 process_random_removing 快大 10 倍对长度 2 000 的列表,个 例了 20如果升仅仅是 25%,或者是一个常数因子 2,那通常个性能差 是忽略的,因会对你的整体用产生什性能影响,但如果算法慢了 10 或 20 倍,情况就了种怕的效会整个程序的瓶颈们谈到 O(n2)和 O(n)的行对时,题的性根本无法忽视对种大 O 的行,随着输入 数据的增长,它们耗时间的差无限地递增去 更多资料 Library Reference 和 Python in a Nutshell 中的 random 和 timeit 模块 5.7 在增加元素时保持序列的序 感谢John Nielsen 任务 你需要维护一个序列,个序列断地有新元素加入,但始处排序完的状, 样你在任何需要的时候检查或者删除前序列中小的元素 解决方案 假有一个排序的列表,如 the_list = [903, 10, 35, 69, 933, 485, 519, 379, 102, 402, 883, 1] 调用 the_list.sort( )将列表排序,然用 result = the_list.pop(0)来获得和删除小的 元素但是,每加入一个元素如 the_list.append(0),都需要再调用 the_list.sort 来排序 使用 Python 准的 heapq 模块 搜索和排序 195 import heapq heapq.heapify(the_list) 在列表并一定完了排序,但是它满足堆的特性若所有的索引都是有效的, the_list[i]< = the_list[2*i + 1] the_list[i]< = the_list[2*i+2],所,the_list[0]就是 小的元素了保持堆特性的有效性,们使用 result = heapq.heappop(the_list)来获并 删除小的元素,用 heapq.heappush(the_list, newitem)来加入新的元素如果需要时做 加入一个新元素并删除之前的小的元素,用 result=heapq.heapreplace (the_list, newitem) 论 需要一种有序的方式获数据时每都择你手中有的小元素,择 在获数据时付出行时,或者在加入数据时付出一种方式是将数据放入 列表并对列表排序样,很容易地你的数据按照序小到大排列然而, 你得每在加入新数据时调用 sort,确保每在增加新元素之能够获小 的元素Python 列表的 sort 实采用了一种有的自然的并排序,它的排序开 销经被力地缩了,但然难人接每添加和排序每获 删除,通过 pop的时间,前列表中元素的数目O(N),准确地说 一种方案是使用一种做堆的数据的结构,是一种简洁的树,它能确保 父节点总是子节点小在 Python 中维护一个堆的好方式是使用列表,并用模块 heapq 来管理列表,如本节解决方案所示的那样个列表无完排序,但你 能够确保每你调用 heappop 列表中获元素时,总是得到前小的元素,然所 有节点会被调整,确保堆特性然有效每通过 heappush 添加元素,或者通过 heappop 删除元素,它们所花费的时间都前列表长度的对数O(logN),准确 地说需要付出一点点总体来说,也非常小 举例来说,很适合使用堆方式的场合是样的假有一个很长的队列,并周期性 地有新数据到达,你总是希望能够队列中获要的元素,而无断地新排 序或者在整个队列中搜索个概念做先队列,而堆是适合实它的数据 结构注意,本质,heapq 模块在每调用 heappop 时向你供小的元素,因需 要安排你的元素的先值,反映出元素的个特点举个例子,假你每收到 数据都付出一个钱,而任何时候要的元素都是队列中钱高的那个外, 对钱相的元素,先到达的要要一些面是一个建先队列的类, 们遵循面到的要求并使用了 heapq 模块的函数 class prioq(object): def _ _init_ _(self): self.q = [ ] self.i = 0 def push(self, item, cost): 196 第 5 heapq.heappush(self.q, (-cost, self.i, item)) self.i += 1 def pop(self): return heapq.heappop(self.q)[-1] 段码的意是将钱置负,作元的第一个元素,并将整个元入堆中, 样更高的出会产生更小的元基 Python 的自然较方式在钱之,们放置了 一个递增的索引,样,元素拥有相的钱时,先到达的元素将会处更小的元中 在 Python 2.4 中,heapq 模块被新实和一化了, 5.8 节中更多有关 heapq 的信息 更多资料 Library Reference 和 Python in a Nutshell 中 heapq 模块的文档Python 源码的 heapq.py 中包含了有关堆的一些非常有趣的论5.8 节中更多的关 heapq 的信息19.14 节中 使用 heapq 对完排序的多个序列行合并 5.8 获序列中小的几个元素 感谢Matteo Dell'Amico、Raymond Hettinger、George Yoshida、Daniel Harding 任务 你需要一个给定的序列中获一些小的元素将序列排序,然使用 seq[:n], 但有没有更好的办法呢? 解决方案 如果需要的元素数目 n 小序列的长度,也许能做得更好sort 是很快的,但它的 时间复杂度然是 O(nlogn),但如果 n 很小,们获前 n 个小元素的时间是 O(n) 面给出一个简行的生器,在 Python 2.3 和 2.4 中都样有效 import heapq def isorted(data): data = list(data) heapq.heapify(data) while data: yield heapq.heappop(data) 在 Python 2.4 中,如果先知道 n,有更简和更快的方法 data 中获前 n 个小 的元素 import heapq def smallest(n, data): return heapq.nsmallest(n, data) 搜索和排序 197 论 data 能是任何有边界的迭对象,解决方案中的 isorted 函数通过调用 list 来确保它 是序列也删 data = list(data)一行,假如列条满足的话你知道 data 是一 个序列,你并关心生器是新排列了 data 的元素,而需要在获的时 data 中删除元素 • 如 5.7 节所示,Python 准供了 heapq 模块,它支持人们熟知的数据结 构— 堆解决方案中的 isorted 先建了一个堆通过 heap.heapify,然在每 一获元素的时候通过 heap.heappop,生并删除堆中小的元素 在 Python 2.4 中,heapq 模块引入了个新函数heapq.nlargest (n, data) 返回的是一个 长度 n 的 data 中大的元素的列表,heapq.nsmallest (n, data) 返回一个包含前 n 个小元素的列表些函数并要求 data 满足堆的条,它们甚要求 data 是 一个列表—任何有边界的元素是较的迭对象都适用解决方案中的函 数 smallest 除了调用 heapq.smallest,实什也没 关度,们总是该行实际测量,猜测码段的相对行度是毫无意 的行因,们是循获前几个小元素的时候,isorted 的性能和 Python 2.4 内建的 sorted 函数的性能来究怎样呢?了行测试,写了一个 top10 函数,它调用种方法,然也 Python 2.3 实了一个 sorted 函数, 因在 Python 2.3 中函数并得到原生的支持 try: sorted except: def sorted(data): data = list(data) data.sort( ) return data import itertools def top10(data, howtosort): return list(itertools.islice(howtosort(data), 10)) 在的计算机,在 Python 2.4 中处理一个洗过牌的 1 000 个整数的列表,top10 调用 isorted 耗时 260μs,但采用内建的 sorted 耗时 850μs而 Python 2.3 甚要慢得多 isorted 耗时 12ms,sorted 耗时 2.7ms换话说,Python 2.3 的 sorted Python 2.4 的 sorted 要慢 3 倍,但在 isorted 要慢 50 倍需要记个要的经验需要行 化时,首先行测量该仅仅根据基本原理择化的方式,因真实的性能 数据化多端,使在个兼容的发行本之间也会有很大的差第个经验是 如果真的很关心性能,那紧转移到 Python 2.4 和 Python 2.3 相,Python 2.4 经 过了极大的化和加,特别是在搜索和排序的方面 如果确定你的码需要支持 Python 2.4,那,如本节解决方案所建议的那样, 198 第 5 使用 heapq 的新函数 nsmallest它度快使用简便,胜自编写码举个例 子,实 Python 2.4 中的 top10,你需要 import heapq def top10(data): return heapq.nsmallest(10, data) 前面展示的那个基 isorted 的 top10,对样的洗过牌的 1 000 个整数的列表行 处理,耗的时间削减一半 更多资料 Library Reference 和 Python in a Nutshell 中 list 类型的 sort 方法, heapq 和 timeit 模 块第 19 中关 Python 的迭的内容Python in a Nutshell 中有关化的节 Python 源码中的 heap.py 所包含的关堆的有趣的论5.7 节关 heapq 的绍 5.9 在排序完的序列中元素 感谢Noah Spurrier 任务 你需要序列中的一系列元素 解决方案 如果列表 L 经是排序完的状, Python 准供的 bisect 模块很容易地 检查出元素 x 是在 L 中 import bisect x_insert_point = bisect.bisect_right(L, x) x_is_present = L[x_insert_point-1:x_insert_point] == [x] 论 对 Python 来说,在列表 L 中一个元素 x 是很简的任务要检查元素是在, if x in L要知道 x 的确置,L.index(x)然而,L 的长度 n,些操作所花费的 时间 n ,因它们是循地检查每一个元素,看看是 x 相等实, 如果 L 是排过序的,们做得更好 在完了排序的序列中元素的经算法就是搜索,因每一它都将查的 范围减半—一般情况它需要log2n完搜索但需要多查某些元素时, 个题就很值得仔探了,通过使用一些方法,多搜索量少付出一些开 销在调用L.sort( )之,一旦你决定用搜索在L中查x,该马想到Python准 的bisect模块 搜索和排序 199 体地说,们需要 bisect.bisect_right 函数来保持原列表的排序状,它将返回一个索 引,指示出们要插入的元素的置,而它也会修改列表如果列表中经在 相等的元素了,bisect_right 将返回拥有相值的元素边邻接的索引因,在调用 bisect.bisect_right(L, x)获得了插入点之,需立刻检查插入点之前的置,看看 是经有一个等 x 的元素在了 解决方案中计算 x_is_present 的方式能是那直如果知道 L 是空列表,们 能写得更简更直 x_is_ resent = L[x_insert_point-1] == x p 过,个更简的方式在对空列表行索引操作的时候会引发常的边界 无效时,操作索引操作更加谨,因它是生了一个空的,没有 引发任何常一般来说, i 是 somelist 的有效索引时,somelist[i:i+1]是和[somelist[i]] 一样的元素列表,但索引操作引发 IndexError 常时,它是一个空的列表[ ]对 x_is_present 的计算充利用了种要的特性,避免了处理常,时也能统一的 方式对 L 处理空和非空的情况一个的方式是 x_is_ resent = L and L[x_insert_point-1] == x p 个方式利用了 and 的短路的行模式来保护索引操作,避免了使用操作 如果元素是哈希的意味着元素被用作 dict 的键,如 5.12 节的做法一样,使用 一个辅助的 dict 也是一个行的方法过,若元素是较的comparable,若 较,排序是能实行的哈希的因无法将它们键用,本节的 个基经排序的列表的方法就能是唯一行的方案了 如果列表经排序完,而需要查的元素数目是非常多,那大多数情况, 用 bisect 要构建一个辅助快,因在构建的时间投资无法被大量查 摊尤是在 Python 2.4 中,bisect 经被高度化,在 Python 2.3 中的对本要 快得多如,在的计算机,用 bisect.bisect_right 在含有 10 000 个元素的列表中 查一个元素,Python 2.4 要 Python 2.3 快 4 倍 更多资料 Library Reference 和 Python in a Nutshell 中的 bisect 模块5.12 节 5.10 序列中小的第 n 个元素 感谢Raymond Hettinger、David Eppstein、Shane Holloway、Chris Perkins 任务 需要根据排序序列中获得第 n 个元素如,中间的元素,也被中值如 200 第 5 果序列是经排序的状,该使用 seq[n],但如果序列被排序,那除了先对整 个序列行排序之外,有没有更好的方法? 解决方案 如果序列很长,洗牌洗得很充,而元素之间的较开销也大,那也许能到 更好的方式排序的确很快,但管怎样,它一个长度 n 的充洗牌的序列的 时间复杂度然是 O(nlogn),而时间复杂度 O(n)的得第 n 个小元素的算法也的 确是在的面们给出一个函数来实算法 import random def select(data, n): " 寻找第 n 个元素 (最小的元素是第 0 个). " # 创建一个新列表,处理小于 0 的索引,检查索引的有效性 data = list(data) if n<0: n += len(data) if not 0 <= n < len(data): raise ValueError, "can't get rank %d out of %d" % (n, len(data)) # 主循环,看上去类似于快速排序但不需要递归 while True: pivot = random.choice(data) pcount = 0 under, over = [ ], [ ] uappend, oappend = under.append, over.append for elem in data: if elem < pivot: uappend(elem) elif elem > pivot: oappend(elem) else: pcount += 1 numunder = len(under) if n < numunder: data = under elif n < numunder + pcount: return pivot else: data = over n -= numunder + pcount 论 本节解决方案也用复的元素举个例子,列表[1, 1, 1, 2, 3]的中值是 1,因 它 是 将 5 个元素按序排列之的第 3 个如果由某些特别的原因,你想考虑复, 而需要缩减个列表,使得所有元素都是唯一的如,通过 18.1 节供的方法, 完一骤之再回到本节的题 搜索和排序 201 输入参数 data 是任意有边界的迭对象首先们对它调用 list 确保得到迭 的对象,然入持续循过程在循的每一中,首先随机出一个轴心元素 轴心元素基准,将列表个部,一个部高轴心,一个部 轴心然继续在一轮循中对列表的个部中的一个行深入处理,因 们判断第 n 个元素处哪一个部,所外一个部就丢了个 算法的思想很接经的快排序算法过快排序无法丢任何部,它必 用递的方法,或者用一个来换递,确保对每个部都行了处理 随机择轴心使得个算法对任意序的数据都适用但原生的快排序, 某些序的数据将极大地影响它的度个实花费大log2N时间用调用 random.choice一个值得注意的是算法统计了出轴心元素的数,是了在一些 特殊情况能够保证较好的性能,如data中能含有大量的复数据 将局部量列表 under 和 over 的被绑定方法 append 抽出来,看来没什意,而 增加了一点小小的复杂性,但实际,是 Python 中一个非常要的化术 了保持编译器的简直接预期性健壮性,Python 会将一些恒定的计算 移出循,它也会缓对方法的查询结果如果你在内层循调用 under.append 和 over.append,每一轮都会付出开销和如果想把某些情一性做好,那需 要自动手完你考虑化题时,你总是该对化前和化的效率, 确保化真到了作用根据的测试,对获 range(10 000)的第 5 000 个元素 样的任务,去化部之,性能降了 50%虽然增加一点小小的复杂性,但 然是值得的,那是 50%的性能差 关化的一个自然的想法是,在循中调用 cmp(elem, pivot),而是用一些独立的 elem < piovt 或 elem > pivot 来测试幸的是,cmp 会高度实它有能 会降,少 data 的元素是一些基本类型如数和符串的时候,的确是样 那,select 的性能和面个简方法的性能相如何呢? def selsor(data, n): data = list(data) data.sort( ) return data[n] 在的计算机,获一个 3 001 个整数的充洗牌的列表的中值,本节解决方案的 码耗时 16ms,而 selsor 耗时 13ms,再考虑到 sort 在序列部有序的情况度会更快, 元素是基本类型较操作执行得很快,而列表长度也大,所使用 select 并没有 什势将长度增加到 30 001,个方法的性能得非常接,都是 170ms但 将列表长度修改 300 001,select 表出了势,它用了 2.2s 获得了中值, 而 selsor 需要 2.5s 但如果序列中元素的较操作非常耗时,那个方式表出的大衡就被 202 第 5 彻打破了,因个方式的关键的差就是较操作执行的数—select 执行 O(n),而 selsor 执行 O(nlogn)举个例子,假如们需要较的是某个类的实例, 较操作的开销相大模拟某些四维的点,前维通常总是相的 class X(object): def _ _init_ _(self): self.a = self.b = self.c = 23.51 self.d = random.random( ) def _dats(self): return self.a, self.b, self.c, self.d def _ _cmp_ _(self, oth): return cmp(self._dats, oth._dats) 在,使对 201 个实例求中值,select 就经表得 selsor 快了 换话说,基列表的 sort 方法的实的确要简洁得多,实 select 也确实需要多付出 一点力气,但如果 n 足够大而较操作的开销也无法忽略的话,select 就体出它的 值了 更多资料 Library Reference 和 Python in a Nutshell 文档中关 list 类型的 sort 方法, random 模块 5.11 行码的快排序 感谢Nathaniel Gray、Raymond Hettinger、Christophe Delord、Jeremy Zucker 任务 你想证明,Python 对函数式编程范式的支持第一眼看去的印象强多了 解决方案 函数式编程语言非常漂亮,如 Haskell 就是个例子,但 Python 的表也遑多 def qsort(L): if len(L) <= 1: return L return qsort([lt for lt in L[1:] if lt < L[0]]) + L[0:1] + \ qsort([ge for ge in L[1:] if ge >= L[0]]) 个人认,述码和 Haskell 的本http://www.haskell.org来一点也 色 qsort [ ] = [ ] qsort (x:xs) = qsort elts_lt_x ++ [x] ++ qsort elts_greq_x where 搜索和排序 203 elts_lt_x = [y | y <- xs, y < x] elts_greq_x = [y | y <- xs, y >= x] 面给出一个 Python 本的测试函数 def qs_test(length): import random joe = range(length) random.shuffle(joe) qsJoe = qsort(joe) for i in range(len(qsJoe)): assert qsJoe[i] == i, 'qsort is broken at %d!' %i 论 相快排序的原生实,们给出的实能展列表推的强大表力但千万 要在真实的码中使用种方法Python 列表有一个 sort 方法,度要们的实 快得多,无疑该是首的方案在 Python 2.4 中,新的内建函数 sorted 接任意的 有限序列并返回一个新的完了排序的序列段码的唯一用处是向朋们炫, 尤是那些函数式编程的狂热子,如 Haskell 语言的爱好者 在 http://www.haskell.org/aboutHaskell.html 看到了 Haskell 的漂亮的快排序之,产 生了写一个 Python 的对本的想法在看到 Haskell 的函数的表能力之, 意识到借助 Python 中的列表推也能样方式实除了使用 Haskell 学来 的列表推,们也法加入了很多 Python 风格的东西 个实都是列表的第一个元素轴心,因而对一个普通的,经完排序的 列表它们会有差的性能表 O(n)绝该在工作码中使用它,但既然本节的目 是显摆,那也无所谓了 写一个没有那紧凑,但有样结构的本,并使用命的局部量和函数 来增加清晰度和读性 def qsort(L): if not L: return L pivot = L[0] def lt(x): return x=pivot return qsort(filter(lt, L[1:]))+[pivot]+qsort(filter(ge, L[1:])) 一旦沿着条思路走去,就很容易地对原来的做法继续改,如使用一个随 机的轴心元素来能地避免糟糕的情况,并对用的轴心计数,来对序列中有 多相等地元素的情况 import random def qsort(L): if not L: return L pivot = random.choice(L) 204 第 5 def lt(x): return xpivot return qsort(filter(lt, L))+[pivot]*L.count(pivot)+qsort(filter(gt, L)) 虽然码得到了增强,但是看去没那有趣了,而利炫而真的工作 别的排序码是一码管们多喜些看去爱的码,它们在性能和 稳固度方面永也无法和 Python 内建的排序相并论 除了高清晰度和健壮性,们把力气用在相反的方向,利用 Python 的 lambda 写出更紧凑和晦涩的东西 q=lambda x:(lambda o=lambda s:[i for i in x if cmp(i,x[0])==s]: len(x)>1 and q(o(-1))+o(0)+q(o(1)) or x)( ) 少,它足够漂亮一行码,然由长度的题好被截行,但你该明 ,真实的用绝对该是样的们用读性较好的 def 语来换晦涩难懂 的 lambda,得到的等码然是很好读 def q(x): def o(s): return [i for i in x if cmp(i,x[0])==s] return len(x)>1 and q(o(-1))+o(0)+q(o(1)) or x 但如果们把那个过精炼的 len(x)>1 and . . . or x 开,并用 if/else 语,时 引入一些局部,清晰度似乎高了少 def q(x): if len(x)>1: lt = [i for i in x if cmp(i,x[0]) == -1 ] eq = [i for i in x if cmp(i,x[0]) == 0 ] gt = [i for i in x if cmp(i,x[0]) == 1 ] return q(lt) + eq + q(gt) else: return x 幸在真实世界中,Python 用户并喜写出那种盘绕的层层叠叠的充满 lambda 的 码实,很多但也得认是所有人都对 lambda 感到烦能是由看到过 多的对 lambda 的滥用,因都量地使用读性较好的 def 语因,Python 并 要求用户有能够乱麻一般的码中解码的能力,但也许别的语言是需要种能力的 语言的任何一个特性都能被程序员滥用,显得更聪明样的结果是,一些 Python 用户虽然是少数甚对列表推列表推被揉一堆物,和简的 for 循相,的确看去没那清晰和 and/or 操作符的短路行模式因利用它们 写出非常晦涩精炼的码,完全无法和 if 语的清晰易读相也感到恐惧 更多资料 Haskell 的页,http://www.haskell.org 搜索和排序 205 5.12 检查序列的员 感谢Alex Martelli 任务 你需要对一个列表执行很频繁的员资格检查而 in 操作符的 O(n)时间复杂度对性能 的影响很大,你也能将序列转化一个或者合,因你需要保留原序列的 元素序 解决方案 假需要给列表添加一个在该列表中在的元素一个行的方法是写样一个 函数 def addUnique(baseList, otherList): auxDict = dict.fromkeys(baseList) for item in otherList: if item not in auxDict: baseList.append(item) auxDict[item] = None 如果你的码是在 Python 2.4 行,那将辅助换辅助合效果是完全 一样的 论 面给出一个简真?的方式,看去相错 def addUnique_simple(baseList, otherList): for item in otherList: if item not in baseList: baseList.append(item) 如果列表很短的话,个方法倒也没题 但是,如果列表是很短,个简的方法会非常慢你用 if item not in baseList 样的码行检查时,Python 会用一种方式执行 in 操作对列表 baselist 的元素 行内部的循遍历,如果到一个元素等 item 返回 True,如果直到循结束也 没有发相等的元素返回 Falsein 操作的均执行时间是 len(baseList)的 addUnique_simple 执行了 len(otherList) in 操作,因它耗的时间个列表 长度的乘 而解决方案给出的 addUnique 函数,首先建了一个辅助的 auxDict,一的时 间 len(baseList)然在循中检查 dict 的员—是大差的一, 206 第 5 因检查一个元素是处一个 dict 中的时间大是一个常数,而 dict 中元素的数 目没有关系因,那个 for 循耗的时间 len(otherList),样,整个函数所 需要的时间就个列表的长度之和 对行时间的析挖得更深一点,因在 addUnique_simple 中 baseList 的长度 并是一个常量每到一个属 baseList 的元素,baseList 的长度就会增加但 样的析结果会前面的简化的结果有大出入们准备一些用例行 测试每个列表中有 10 个整数有 50%的叠时,简化解决方案给出的函数慢 30%,样的性能降忽略若每个列表都有 100 个整数,而然有 50%的 叠部,简化解决方案的函数慢 12 倍—种别的减效果就无法忽略了,而 列表得更长的时候,情况也得更糟 有时,将一个辅助的 dict 和序列一使用并封装一个对象能高你的用程序的性能 但在个例子中,必在序列被修改时断地维护 dict,保证它总是和序列前所拥 有的元素保持个维护任务并是很简,们有很多方法来实面 给出一种时的方式,需要检查某元素,或者的内容能经无法和 列表内容保持时,们就新构建一个辅助 dict由开销很小,面的类化了 index 方法和员检查部的码 class list_with_aux_dict(list): def _ _init_ _(self, iterable=( )): list._ _init_ _(self, iterable) self._dict_ok = False def _rebuild_dict(self): self._dict = { } for i, item in enumerate(self): if item not in self._dict: self._dict[item] = i self._dict_ok = True def _ _contains_ _(self, item): if not self._dict_ok: self._rebuild_dict( ) return item in self._dict def index(self, item): if not self._dict_ok: self._rebuild_dict( ) try: return self._dict[item] except KeyError: raise ValueError def _wrapMutatorMethod(methname): _method = getattr(list, methname) def wrapper(self, *args): # 重置字典的 OK 标志,然后委托给真正的方法 self._dict_ok = False return _method(self, *args) #只适用于 Python 2.4 wrapper._ _name_ _ = _method._ _name_ _ 搜索和排序 207 setattr(list_with_aux_dict, methname, wrapper) for meth in 'setitem delitem setslice delslice iadd'.split( ): _wrapMutatorMethod('_ _%s_ _' % meth) for meth in 'append insert pop remove extend'.split( ): _wrapMutatorMethod(meth) del _ rapMethod # 删除辅助函数,已经不再需要它了 w list_with_aux_dict 扩展了 list,并 将 原 list 的所有方法然托给它,除了_ _contains_ _ 和 index所有能够修改 list 的方法都被封装了一个包,该包负责置一个志, 确保辅助的有效性Python 的 in 操作符调用_ _contains_ _方法除非志被 置, list_with_aux_dict 的_ _contains_ _方法会建辅助志被置时,建 没有必要,而 index 方法然像原先一样工作 述 list_with_aux_dict 类并没有用帮助函数列表的所有属性方法绑定和安装一个 包,而是所需,们也在 list_with_aux_dict 的体中写出所有的 def 语来 wrapper 方法但是述码有个要的点是除了冗余和复复和啰嗦的 码人生,而容易滋生 bugPython 在自省和动改方面的能力给你供了一 个择建一个 wrapper 方法,用一种聪明而简的方式或者,如果你想避免 使用被人黑魔法的类对象的自省和动改,也写一堆复啰嗦的码 list_with_aux_dict 的结构很适合通常的使用模式,对序列的修改操作一般总是中出 ,然接着会有一段时间序列无被修改,但需要检查元素的员资格如果参 数 baseList 是一个普通的列表,而是 list_with_aux_dict 的一个实例,早先展示的 addUnique_simple 函数也会因得到任何性能的升,因个函数会交地行 员资格检查和序列修改因,类 list_with_aux_dict 中过多的辅助的建影响 了函数的性能除非是针对某个特例,如 otherList 中绝大多数元素都经在 baseList 中出过了,因对序列的修改相对元素的检查,发生的数要少得多 对些员资格的检查所做的化有个要的前,序列中的值必是哈希的 然的话,它们能被用来做的键或者合的元素举个例子,元的列表适用 本节的解决方案,但对列表的列表,们恐怕得外想办法了 更多资料 Library Reference 和 Python in a Nutshell 中关序列类型和映射类型的节 5.13 子序列 感谢David Eppstein、Alexander Semenov 任务 你需要在某大序列中查子序列 208 第 5 解决方案 如果序列是符串普通的或者 Unicode,Python 的符串的 find 方法准的 re 模块是好的工,该使用 Knuth-Morris-Pratt 算法KMP def KnuthMorrisPratt(text, pattern): ''' 在序列 text 中找到 pattern 的子序列的起始位置 每个参数都可以是任何可迭代对象 在每次产生一个结果时,对 text 的读取正好到达包括 对 pattern 的一个匹配 ''' # 确保能对 pattern 进行索引操作,同时制作 pattern 的 # 一个拷贝,以防在生成结果时意外地修改 pattern pattern = list(pattern) length = len(pattern) # 创建 KMP“偏移量表”并命名为 shifts shifts = [1] * (length + 1) shift = 1 for pos, pat in enumerate(pattern): while shift <= pos and pat != pattern[pos-shift]: shift += shifts[pos-shift] shifts[pos+1] = shift # 执行真正的搜索 startPos = 0 matchLen = 0 for c in text: while matchLen == length or matchLen >= 0 and pattern[matchLen] != c: startPos += shifts[matchLen] matchLen -= shifts[matchLen] matchLen += 1 if matchLen == length: yield startPos 论 本节实的 Knuth-Morris-Pratt 算法被用在一个大文本的连续序列中查某种指定 的模式由 KMP 是序的方式文本,所很自然地,们也将用 包括文本在内的任意迭对象在处理阶段,算法会建一个关偏移量的表,它 耗的时间模式的长度,而每个文本志恒定的时间处理在所有关文 本处理的基础算法书中都看到 KMP 算法的解释和示例更多资料一栏中也供 了推荐读物 如果 text 和 pattern 都是 Python 符串,通过使用 Python 的内建搜索方法得到一 个更快的方案 def finditer(text, pattern): pos = -1 while True: pos = text.find(pattern, pos+1) 搜索和排序 209 if pos < 0: break yield pos 如,使用一个长度 4 的母表ACGU,在长度 100000 的文本中查长度 8 的一个模式,在的计算机,借助 finditer 函数耗时 4.3ms,但使用 KnuthMorrisPratt 函数执行样任务需要 540ms在 Python 2.3 中而在 Python 2.4 会快一些, 480ms, 但然 finditer 慢了超过 100 倍所请记本节的算法适用在通用的序列中 行搜索,包括那些数据量大到无法放入内的情况,如果需要对符串搜索, Python 内建的搜索方法有完全倒性的势 更多资料 关基础算法的秀书籍有很多中一本备推崇的书是 Thomas H. CormenCharles E. LeisersonRonald L. RivestClifford Stein,合著的 Introduction to AlgorithmsMIT Press,第 5.14 给类型增加排功能 感谢Dmitry Vasiliev、Alex Martelli 任务 你需要用储一些键和数的映射关系你经常需要自然序数 的升序键和数值,并能够根据那个序检查一个键的排对个题,用 dict 似乎合适 解决方案 们使用 dict 的子类,根据需要增加或者写一些方法在们使用多继将 UserDict.DictMixin 放置在基类 dict并仔安排种方法的托或写之前,们 法获得一种美妙的衡,既拥有极好的性能避免了编写一些冗余码 们在文档符串中加入很多示例,用准的 doctest 模块来供元测 试的功能,也能够确保们在文档符串中编写的例子的准确性 #!/usr/bin/env python ''' 一个反映键到分数的映射的字典 ''' from bisect import bisect_left, insort_left import UserDict class Ratings(UserDict.DictMixin, dict): """ Ratings 类很像一个字典,但有一些额外特性每个键 的对应值都是该键的“分数”,所有键都根据它们的 分数排名。对应值必须是可以比较的,同样,键则必须 是可哈希的即可以“绑”在分数上 210 第 5 所有关于映射的行为都如同预期一样,比如 >>> r = Ratings({"bob": 30, "john": 30}) >>> len(r) 2 >>> r.has_key("paul"), "paul" in r (False, False) >>> r["john"] = 20 >>> r.update({"paul": 20, "tom": 10}) >>> len(r) 4 >>> r.has_key("paul"), "paul" in r (True, True) >>> [r[key] for key in ["bob", "paul", "john", "tom"]] [30, 20, 20, 10] >>> r.get("nobody"), r.get("nobody", 0) (None, 0) 除了映射的接口,我们还提供了和排名相关的方法。 r.rating(key)返回了某个键的排名,其中排名为 0 的 是最低的分数如果两个键的分数相同,则直接比较 它们两者,“打破僵局”,较小的键排名更低 >>> [r.rating(key) for key in ["bob", "paul", "john", "tom"]] [3, 2, 1, 0] getValueByRating(ranking)和 getKeyByRating(ranking)对于 给定的排名索引,分别返回分数和键 >>> [r.getValueByRating(rating) for rating in range(4)] [10, 20, 20, 30] >>> [r.getKeyByRating(rating) for rating in range(4)] ['tom', 'john', 'paul', 'bob'] 一个重要的特性是 keys( )返回的键是以排名的升序排列的, 而其他所有返回的相关的列表或迭代器都遵循这个顺序 >>> r.keys( ) ['tom', 'john', 'paul', 'bob'] >>> [key for key in r] ['tom', 'john', 'paul', 'bob'] >>> [key for key in r.iterkeys( )] ['tom', 'john', 'paul', 'bob'] >>> r.values( ) [10, 20, 20, 30] >>> [value for value in r.itervalues( )] [10, 20, 20, 30] >>> r.items( ) [('tom', 10), ('john', 20), ('paul', 20), ('bob', 30)] >>> [item for item in r.iteritems( )] [('tom', 10), ('john', 20), ('paul', 20), ('bob', 30)] 实例可以被修改添加、改变和删除键 -分数对应关系 而且实例的每个方法都反映了实例的当前状态 >>> r["tom"] = 100 >>> r.items( ) [('john', 20), ('paul', 20), ('bob', 30), ('tom', 100)] 搜索和排序 211 >>> del r["paul"] >>> r.items( ) [('john', 20), ('bob', 30), ('tom', 100)] >>> r["paul"] = 25 >>> r.items( ) [('john', 20), ('paul', 25), ('bob', 30), ('tom', 100)] >>> r.clear( ) >>> r.items( ) [ ] """ ''' 这个实现小心翼翼地混合了继承和托管,因此在尽量减少 冗余代码的前提下获得了不错的性能,当然,同时也保 证了语义的正确性。所有未被实现的映射方法都通过 继承来获得,大多来自 DictMixin,但关键的 _ _getitem_ _ 来自 dict。''' def _ _init_ _(self, *args, **kwds): ''' 这个类就像 dict 一样被实例化 ''' dict._ _init_ _(self, *args, **kwds) # self._rating 是关键的辅助数据结构一个所有值,键 #的列表,并保有一种“自然的”排序状态 self._rating = [ (v, k) for k, v in dict.iteritems(self) ] self._rating.sort( ) def copy(self): ''' 提供一个完全相同但独立的拷贝 ''' return Ratings(self) def _ _setitem_ _(self, k, v): ''' 除了把主要任务委托给 dict,我们还维护 self._rating ''' if k in self: del self._rating[self.rating(k)] dict._ _setitem_ _(self, k, v) insort_left(self._rating, (v, k)) def _ _delitem_ _(self, k): ''' 除了把主要任务委托给 dict,我们还维护 self._rating ''' del self._rating[self.rating(k)] dict._ _delitem_ _(self, k) ''' 显式地将某些方法委托给 dict 的对应方法,以免继承了 DictMixin 的较慢的虽然功能正确实现 ''' _ _len_ _ = dict._ _len_ _ _ _contains_ _ = dict._ _contains_ _ has_key = _ _contains_ _ ''' 在 self._rating 和 self.keys( )之间的关键的语义联系 —DictMixin “免费”给了我们所有其他方法,虽然我们直接实现它们能够 获得稍好一点的性能。 ''' def _ _iter_ _(self): for v, k in self._rating: yield k iterkeys = _ _iter_ _ def keys(self): return list(self) 212 第 5 ''' 三个和排名相关的方法 ''' def rating(self, key): item = self[key], key i = bisect_left(self._rating, item) if item == self._rating[i]: return i raise LookupError, "item not found in rating" def getValueByRating(self, rating): return self._rating[rating][0] def getKeyByRating(self, rating): return self._rating[rating][1] def _test( ): ''' 我们使用 doctest 来测试这个模块,模块名必须为 rating.py,这样 docstring 中的示例才会有效 ''' import doctest, rating doctest.testmod(rating) if _ _name_ _ == "_ _main_ _": _test( ) 论 在很多方面,都是很自然地被用储键如,赛中参者的和 数如参者获得的数,或者参者在拍中的出的对关系的数据结构 如果们希望在些用中使用,们能会希望自然的序—键对 的数的升序—们也希望能够获得基前数的排如,参 者在排在第,排在第的参者的数,等等 了达到个目的,本节给 dict 的子类增加了一些它本身完全备的功能rating 方 法getValueByRatinggetKeyByRating,时,关键和妙的地方是,们修改了 keys 方法和他相关的方法,样它们就能返回按照指定序排列的列表或者迭 对象如按照数的升序排列对个有样数的键,们继续较键本身 大多数的文档都放在类的文档符串中—保留文档和示例是很要的,用 Python 准的 doctest 模块来供元测试的功能,确保给出的例子是确的 关个实的有趣之处是,它很关心除冗余那些复和人烦的码,很 能滋生 bug,但时没有损害性能Ratings 类时 dict 和 DictMixin 继,并把 者排在基类列表的第一,因,除非明确地覆盖了基类的方法,Ratings 的方法基 本来自 DictMixin,如果它供了的话 Raymond Hettinger 的 DictMixin 类初是发布在 Python Cookbook 在线本中的一个例 子,来被吸收到了 Python 2.3 的准中DictMixin 供了种映射的方法,除了 _ _init_ _copy四个基本方法_ _getitem_ __ _setitem_ __ _delitem_ _和 keys 如果需要的是一个映射类并想要支持完整映射所有的种方法, DictMixin 派生子类,并供那些基本的方法体依赖你的类的语—如,如果你的 搜索和排序 213 类有修改的实例,你无供属性置方法_ _setitem_ _和_ _delitem_ _ 添加一些的方法升性能,覆盖 DictMixin 所供的原有方法整个 DictMixin 的 架构被看做是一个经的模方法计模式Template Method Design Pattern,它 用一种混合的体供了广泛的适用性 在本节的类中,基类继了_ _getitem_ _准确地说,是内建的 dict 类型继, 出性能的考虑,们把能托的都托给了 dict们必自实基本的属性 置方法_ _setitem_ _和_ _delitem_ _,因除了托给基类的方法,需要维护一个 数据结构 self._rating—是一个列表,包含了许多score, key值对,列表在准 模块 bisect 的帮助完了排序们也新实了 keys在个骤中,新实 了_ _iter_ _, iterkeys,很明显,借助_ _iter_ _更容易地实 keys来利用 self._rating 并按照们需要的序返回键,除了面个和排有关的方法, 们_ _init_ _和 copy 添加了实 个结果是一个很有趣的例子,它得了简洁和清晰的衡,并大化地用了 Python 准的多功能如果你在用程序中使用个模块,测试结果能会显示,本节 的类 DictMixin 继来的方法的性能是人满意, DictMixin 的实是基 必要的通用性的考虑如果它的性能能满足你的要求,自供一个实来获 高性能假有个 Ratings 类的实例 r,你的用程序需要对 r.iteritems ( )的结果 行大量的循处理,给类的体部增加个方法的实获得更好的性能 def iteritems(self): for v, k in self._rating: yield k, v 更多资料 Library Reference 和 Python in a Nutshell 中 UserDict 模块的 DictMixin 类, bisect 模块 5.15 根据的首母将人排序和 感谢Brett Cannon、Amos Newcombe 任务 你想将一人写入一个地址簿,时希望地址簿能够根据的首母行, 按照母序表排序 解决方案 Python 2.4 的新 itertools.groupby 函数使得个任务很简 214 第 5 import itertools def groupnames(name_iterable): sorted_names = sorted(name_iterable, key=_sortkeyfunc) name_dict = { } for key, group in itertools.groupby(sorted_names, _groupkeyfunc): name_dict[key] = tuple(group) return name_dict pieces_order = { 2: (-1, 0), 3: (-1, 0, 1) } def _sortkeyfunc(name): ''' name 是带有名和姓以及可选的中名或首字母的字符串, 这些部分之间用空格隔开返回的字符串的顺序是 姓-名-中名,以满足排序的需要 ''' name_parts = name.split( ) return ' '.join([name_parts[n] for n in pieces_order[len(name_parts)]]) def _groupkeyfunc(name): ''' 返回的键即姓的首字母被用于分组 ''' return name.split( )[-1][0] 论 本节解决方案中的 name_iterable 必是一个迭对象,它的元素是遵循-中-格 式的人符串,中中是的部空格隔开对个迭对象调用 groupnames 得到的结果是一个,它的键是的首母,而对的值是完整的 中和的构的元 管是 是 中 的格式,辅助的_sortkeyfunc 函数都能将人 符串割开,并将部记录到一个列表中,序是先,如果有中,要 加中或首母,将个列表拼接一个符串并返回根据任务的述, 个符串是用来排序的关键Python 2.4 的内建函数 sorted 用个函数它将被用到 每个元素用获排序的键作的 key 的参数 辅助函数_groupkeyfunc 也接样格式的人,并返回的首母—根据题的 述,是们用来将人的关键 方案中的函数 groupnames 使用了个辅助函数和 Python 2.4 的 sorted 和 itertools. groupby 来解决题,建并返回了们要求的 如果想在 Python 2.3 中完个任务,然使用个支持函数并新编写 groupnames由 Python 2.3 的准中并没有供 groupby 函数,先再别对 个排序会更方便一些 def groupnames(name_iterable): name_dict = { } for name in name_iterable: key = _groupkeyfunc(name) 搜索和排序 215 name_dict.setdefault(key, [ ]).append(name) for k, v in name_dict.iteritems( ): aux = [(_sortkeyfunc(name), name) for name in v] aux.sort( ) name_dict[k] = tuple([ n for _ _, n in aux ]) return name_dict 更多资料 19.21 节Library ReferencePython 2.4关 itertools 的文档 216 第 5
  • 还剩215页未读

    继续阅读

    下载pdf到电脑,查找使用更方便

    pdf的实际排版效果,会与网站的显示效果略有不同!!

    需要 8 金币 [ 分享pdf获得金币 ] 1 人已下载

    下载pdf

    pdf贡献者

    ckbh

    贡献于2016-02-17

    下载需要 8 金币 [金币充值 ]
    亲,您也可以通过 分享原创pdf 来获得金币奖励!
    下载pdf