Python 2.0 有什么新变化

作者:

A.M. Kuchling 和 Moshe Zadka

概述

A new release of Python, version 2.0, was released on October 16, 2000. This article covers the exciting new features in 2.0, highlights some other useful changes, and points out a few incompatible changes that may require rewriting code.

Python 的开发工作在版本发布之间绝不会完全停止,总是会有错误修复和改进被持续地提交。 2.0 中包含了大量的小问题修正、一些性能优化、额外的文档字符串和更完善的错误消息;要列出所有这些是不可能的,但它们确实带来了明显的变化。 如果你想查看完整清单可以参阅公开的 CVS 日志。 这些进展归功于为 PythonLabs 工作的五位开发者,他们现在可以付出时间修复问题并获得报酬,同时也归功于迁移到 SourceForge 后在沟通方面的改善。

Python 1.6 将会怎样?

Python 1.6 可以被视为继续履行合同义务的 Python 发布版。 在核心开发团队于 2000 年 5 月离开 CNRI 之后,CNRI 要求创建一个 1.6 发布版,其中包含 CNRI 在 Python 项目上已完成的所有工作。 因此 Python 1.6 代表了截至 2000 年 5 月的 CVS 树的状态,其中最重要的新特性是 Unicode 支持。 当然,5 月之后开发工作仍在继续,因此 1.6 树接受了一些修正以确保它对 Python 2.0 向上兼容。 所以 1.6 仍是 Python 演化过程的组成部分,而不是一个旁支。

那么,你应该对 Python 1.6 保持更多关注吗?也许不必。 1.6final 和 2.0beta1 是在同一天(2000 年 9 月 5 日)发布的,计划在一个月左右的时间内完成 Python 2.0 正式版。 如果你有应用程序需要维护,那么迁移到 1.6 破坏兼容性,修复它们,然后在一个月内又迁移到 2.0 再进行新一轮的兼容性修复的做法似乎没有什么意义;你最好直接迁移到 2.0。 本文档中介绍的大多数真正值得关注的特性都只出现在 2.0 中,因为很多工作都是在 5 月到 9 月之间完成的。

新开发流程

Python 2.0 中最重要的变化可能根本不是代码,而是 Python 的开发方式:在 2000 年 5 月,Python 开发者开始使用 SourceForge 提供的工具来存储源代码、跟踪错误报告以及管理补丁提交队列。 要报告 Python 2.0 的错误或提交补丁,请使用位于 https://sourceforge.net/projects/python/ 的 Python项目页上的错误跟踪和补丁管理器工具。

现在托管在SourceForge的最重要的服务是Python CVS树,这是一个包含Python源代码的版本控制库。以前,大约有7个左右的人可以写入CVS树,所有的补丁都必须由这个短名单上的一个人检查和签到。很明显,这并不是非常可扩展的。通过将CVS树转移到SourceForge,有可能向更多的人授予写访问权;截至2000年9月,有27人能够检查变化,增加了4倍。这使得大规模的改变成为可能,如果它们必须通过一小群核心开发者来过滤,就不会被尝试。例如,有一天Peter Schneider-Kamp想到了放弃K&R C的兼容性,将Python的C源转换为ANSI C。在获得python-dev邮件列表的批准后,他发起了一连串的签到,持续了大约一周,其他开发人员加入了进来帮忙,工作就完成了。如果只有5个人可以接触到写作,那么这项任务可能会被视为 “不错,但不值得花费时间和精力,而且它永远不会完成。

转向使用 SourceForge 的服务显著提高了开发速度。 补丁现在由原提交者以外的人提交、评论、修改,并在不同人员之间来回传递,直到补丁被认为值得检入。 程序错误在一个中心位置被跟踪,并可以分配给特定人员进行修复,我们还可以统计未解决程序错误的数量来衡量进度。 这并不是没有代价的:开发人员现在需要处理更多的电子邮件,关注更多的邮件列表,并且需要为新环境编写专门的工具。 例如,SourceForge 发送的默认补丁和错误通知电子邮件完全无用,所以 Ka-Ping Yee 编写了一个 HTML 屏幕抓取器以便发送更有用的信息。

添加代码的便利性引发了一些初期的成长痛苦,比如代码在准备好之前或未经开发者团队明确同意就被检入。 现在形成的审批流程有点类似于 Apache 团队所使用的流程。 开发者可以对补丁投票:+1、+0、-0 或 -1;+1 和 -1 表示接受或拒绝,而 +0 和 -0 则表示开发者对变更大多持无所谓的态度,但略有正面或负面的倾向。 与 Apache 模型最显著的变化是投票本质上是咨询性的,让拥有终身仁慈独裁者地位的 Guido van Rossum 了解总体意见。 他仍然可以忽略投票结果,并批准或拒绝变更,即使社区不同意他的决定。

实际产生补丁是添加新功能的最后一步,与之前指定一个好的设计相比,这通常比较容易。 对于新功能的讨论往往会变成冗长的邮件列表帖子,使讨论难以跟踪,并且没有人能够阅读每一条发给 python-dev 的帖子。 因此,建议了一个相对正式的流程来编写 Python 增强提案(PEP),该流程借鉴了互联网 RFC 流程。 PEP 是描述拟议新特性的草案文件,并不断修改,直到社区达成共识,接受或拒绝该提案。 引用自 PEP 1 的介绍部分 “PEP Purpose and Guidelines”:

PEP 是 Python Enhancement Proposal 的缩写。 一个 PEP 就是一份设计文档,用来向 Python 社区提供信息,或描述一个 Python 新增特性。 PEP 应当提供对所提议特性的精确的技术规格和原理说明。

我们打算将 PEP 作为提出新特性建议、收集社区对特定问题意见以及为必须加入 Python 的设计决策编写文档的首选机制。 PEP 的作者有责任在社区内部建立共识,并应将对立的观点也记入文档。

请阅读 PEP 1 的其余部分以了解 PEP 编辑流程、风格和格式的详细信息。 PEP 保存在 SourceForge 的Python CVS 树中,尽管它们不是 Python 2.0 发行版的一部分,但也可以从 https://peps.python.org/ 以 HTML 形式获取。 截至 2000 年 9 月,共有 25 个 PEP,从 PEP 201 “Lockstep Iteration” 到PEP 225 “Elementwise/Objectwise Operators”。

Unicode

Python 2.0 中最大的新特性是引入了一种新的基本数据类型:Unicode 字符串。 Unicode 使用 16 位二进制数表示字符,而不是 ASCII 所使用的 8 位,这意味着可以支持 65,536 个不同的字符。

Unicode 支持的最终接口是通过在 python-dev 邮件列表上无数次激烈的讨论达成的,主要由 Marc-André Lemburg 基于 Fredrik Lundh 的 Unicode 字符串类型实现来完成。 详细的接口说明被写成了 PEP 100 “Python Unicode Integration”。 这篇文章只简单地涵盖关于 Unicode 接口的最重要信息。

在 Python 源代码中,Unicode 字符串被写成 u"string"。 任意的 Unicode 字符可以使用新的转义序列 \u_HHHH_ 来表示,其中 HHHH 是一个从 0000 到 FFFF 的 4 位十六进制数字。 现有的 \x_HH_ 转义序列也可以使用,而八进制转义序列可以用于最多到 U+01FF 码位的字符,即表示为 \777

Unicode 字符串和常规字符串一样,是一种不可变的序列类型。 它们可以被索引和切片,但不能原地修改。 Unicode 字符串有一个 encode( [encoding] ) 方法,该方法返回一个以所需编码格式表示的 8 位字符串。 编码格式通过字符串命名,如 'ascii''utf-8''iso-8859-1' 等等。 为实现和注册新的编码格式定义了一个编解码器 API,这些编码格式随后可在整个 Python 程序中使用。 如果未指定编码格式,默认编码格式通常是 7 位 ASCII,不过这可以通过在自定义版本的 site.py 模块中调用 sys.setdefaultencoding(encoding) 函数来更改。

将 8 比特位和 Unicode 的字符串结合使用时将总是会使用默认 ASCII 编码格式强制转换到 Unicode;'a' + u'bc' 的结果将为 u'abc'

新增了一批内置函数,现有的内置函数也被修改为支持 Unicode:

  • unichr(ch) 将返回长度为 1 个字符的 Unicode 字符串,其中包含字符 ch

  • ord(u),其中 u 是长度为 1 个字符的常规或 Unicode 字符串,将以整数形式返回该字符的码位值。

  • 函数 unicode(string [, encoding] [, errors] ) 从 8 位字符串创建一个 Unicode 字符串。 encoding 是一个指定使用编码格式的字符串。errors 参数指定如何处理当前编码格式中无效的字符;传入 'strict' 作为参数值会在有任何编码错误时引发异常,而 'ignore' 会静默忽略错误,'replace' 则在出现问题时使用 U+FFFD 即官方的替换字符。

  • exec 语句,以及各种内置函数如 eval()getattr()setattr() 也会接受 Unicode 字符串和普通字符串。 (修复过程中可能会遗漏一些内置函数;如果你发现一个接受字符串但完全不接受 Unicode 字符串的内置函数,请报告此错误。)

一个新的模块 unicodedata 提供了对 Unicode 字符属性的接口。 例如,unicodedata.category(u'A') 返回 2 个字符的字符串 ‘Lu’,其中 ‘L’ 表示这是一个字母,’u’ 表示这是一个大写字母。 unicodedata.bidirectional(u'\u0660') 返回 ‘AN’,表示 U+0660 是一个阿拉伯数字。

codecs 模块包含查找现有编码格式和注册新编码格式的函数。 除非你想实现一个新的编码格式,否则你最常使用的是 codecs.lookup(encoding) 函数,它返回一个 4 元素的元组: (encode_func, decode_func, stream_reader, stream_writer)

  • encode_func 是一个接受 Unicode 字符串的函数,并返回一个 2 元组 (string, length)string 是一个包含部分(可能是全部) Unicode 字符串转换为指定编码的 8 位字符串,length 告诉你转换了多少 Unicode 字符串。

  • decode_funcencode_func 相反,它接受一个 8 位字符串并返回一个 2 元组 (ustring, length),其中 ustring 是转换得到的 Unicode 字符串,length 是一个整数,表示消耗了多少 8 位字符串。

  • stream_reader 是一个支持从流中解码输入的类。 stream_reader(file_obj) 返回一个支持 read()readline()readlines() 方法的对象。 这些方法都会从指定编码转换并返回 Unicode 字符串。

  • stream_writer 同样是一个支持将输出编码到流中的类。 stream_writer(file_obj) 返回一个支持 write()writelines() 方法的对象。 这些方法期望接收 Unicode 字符串,并在输出时将使用指定的编码格式来转换它们。

例如,以下的代码将 Unicode 字符串写入一个 UTF-8 编码的文件:

  1. import codecs
  2. unistr = u'\u0660\u2000ab ...'
  3. (UTF8_encode, UTF8_decode,
  4. UTF8_streamreader, UTF8_streamwriter) = codecs.lookup('UTF-8')
  5. output = UTF8_streamwriter( open( '/tmp/output', 'wb') )
  6. output.write( unistr )
  7. output.close()

以下的代码则可以从文件中读取 UTF-8 输入:

  1. input = UTF8_streamreader( open( '/tmp/output', 'rb') )
  2. print repr(input.read())
  3. input.close()

支持 Unicode 的正则表达式可以通过 re 模块使用,该模块有一个新的底层实现称为 SRE,由 Secret Labs AB 的 Fredrik Lundh 编写。

添加了一个 -U 命令行选项,使 Python 编译器将所有字符串字面量解释为 Unicode 字符串字面量。 这用于测试和为你的 Python 代码提供未来保障,因为未来某个版本的 Python 可能会取消对 8 位字符串的支持,只提供 Unicode 字符串。

列表推导式

列表是 Python 中的一种主力数据类型,许多程序在某个时候都会处理列表。 对列表的两种常见操作是遍历它们,并筛选出符合某个条件的元素,或对每个元素应用某个函数。 例如,给定一个字符串列表,你可能想要提取出所有包含特定子字符串的字符串,或去掉每行的尾随空白。

现有的 map()filter() 函数可以用于此目的,但它们需要一个函数作为参数之一。 如果有一个现有的内置函数可以直接传递,这是很好的,但如果没有,你必须创建一个小函数来完成所需的工作。 而 Python 的作用域规则会使结果变得丑陋,特别是如果这个小函数需要额外的信息。 以上一段中的第一个例子为例,找到列表中所有包含给定子字符串的字符串。 你可以写如下代码来实现:

  1. # 给定列表 L,创建一个由所有包含
  2. # 子字符串 S 的字符串组成的列表。
  3. sublist = filter( lambda s, substring=S:
  4. string.find(s, substring) != -1,
  5. L)

由于 Python 的作用域规则,将会使用默认参数以使由 lambda 表达式创建的匿名函数知道正在搜索哪个子字符串。 列表推导能使这个过程更简洁:

  1. sublist = [ s for s in L if string.find(s, S) != -1 ]

列表推导式的形式如下:

  1. [ expression for expr in sequence1
  2. for expr2 in sequence2 ...
  3. for exprN in sequenceN
  4. if condition ]

for…:keyword:!in 子句包含要迭代的序列。 这些序列不必具有相同的长度,因为它们 不是 并行迭代的,而是从左到右依次迭代;这一点将在以下段落中更清楚地解释。 生成列表的元素将是 表达式 的连续值。 最后的 if 子句是可选的;如果存在,只有当 condition 为真时,表达式 才会被求值并添加到结果中。

为了使语义更为清晰,列表推导相当于以下 Python 代码:

  1. for expr1 in sequence1:
  2. for expr2 in sequence2:
  3. ...
  4. for exprN in sequenceN:
  5. if (condition):
  6. # Append the value of
  7. # the expression to the
  8. # resulting list.

这意味着当有多个 for…:keyword:!in 子句时,生成的列表将等于所有序列长的的乘积。 如果你有两个长度为 3 的列表,输出列表将有 9 个元素:

  1. seq1 = 'abc'
  2. seq2 = (1,2,3)
  3. >>> [ (x,y) for x in seq1 for y in seq2]
  4. [('a', 1), ('a', 2), ('a', 3), ('b', 1), ('b', 2), ('b', 3), ('c', 1),
  5. ('c', 2), ('c', 3)]

为了避免在 Python 的语法中引入歧义,如果 表达式 创建的是一个元组,它必须用括号括起来。 下面的第一个列表推导式有语法错误,而第二个则是正确的:

  1. # Syntax error
  2. [ x,y for x in seq1 for y in seq2]
  3. # Correct
  4. [ (x,y) for x in seq1 for y in seq2]

列表推导的概念最初来自函数式编程语言 Haskell (https://www.haskell.org)。 Greg Ewing 最有力地提出了将其添加到 Python 中的建议,并编写了最初的列表推导式补丁,然后在 python-dev 邮件列表上进行了看似无休止的讨论,并由 Skip Montanaro 保持更新。

增强赋值

增强赋值运算符,另一个长期以来要求添加的功能,已经被加入到 Python 2.0 中。 增强赋值运算符包括 +=-=*= 等。例如,语句 a += 2 将变量 a 的值增加 2,等同于稍长一些的 a = a + 2

支持的完整赋值运算符列表包括 +=-=*=/=%=**=&=|=^=>>=<<=。 Python 类可以通过定义名为 __iadd__()__isub__() 等方法来重载增强赋值运算符。 例如,以下的 Number 类储存一个数字,并支持使用 += 来创建一个递增值的新实例。

  1. class Number:
  2. def __init__(self, value):
  3. self.value = value
  4. def __iadd__(self, increment):
  5. return Number( self.value + increment)
  6. n = Number(5)
  7. n += 3
  8. print n.value

__iadd__() 特殊方法使用增量值调用,并应返回一个具有适当修改值的新实例;这个返回值将作为左侧变量的新值被绑定。

增强赋值运算符最早在 C 编程语言中引入,大多数 C 派生语言,如 awk,C++,Java 和 PHP 也支持它们。 增强赋值补丁由 Thomas Wouters 实现。

字符串的方法

直到现在,字符串操作功能都在 string 模块中,它通常是用 C 编写的 strop 模块的前端。 Unicode 的添加为 strop 模块带来困难,因为所有函数都需要重写,以接受 8 位或 Unicode 字符串,对于像 string.replace() 这样的函数,它需要 3 个字符串参数,这意味着有 8 种可能的排列方式,相应的代码也会变得复杂。

相反,Python 2.0 将这个问题推给了字符串类型,使字符串操作功能通过 8 位字符串和 Unicode 字符串上的方法来实现。

  1. >>> 'andrew'.capitalize()
  2. 'Andrew'
  3. >>> 'hostname'.replace('os', 'linux')
  4. 'hlinuxtname'
  5. >>> 'moshe'.find('sh')
  6. 2

有一件事没有改变,即使是值得注意的愚人节玩笑,Python 字符串仍然是不可变的。 因此,字符串方法返回的是新的字符串,而不是修改他们操作的原字符串。

旧的 string 模块仍然存在以保持向向兼容,但它主要作为新字符串方法的前端。

在 2.0 之前的版本中没有对应方法的两个方法是 startswith()endswith(),尽管它们在 JPython 中存在了相当长的时间。 s.startswith(t) 等同于 s[:len(t)] == t,而 s.endswith(t) 等同于 s[-len(t):] == t

另一个值得特别提及的方法是 join()。 字符串的 join() 方法接收一个参数,即字符串序列,并且等同于来自旧的 string 模块的 string.join() 函数,但参数顺序相反。 换句话说,s.join(seq) 等同于旧的 string.join(seq, s)

循环的垃圾回收

Python 的 C 实现使用引用技术来实现垃圾回收。 每个 Python 对象维护一个指向自身的引用数量,并在引用创建或销毁时调整该计数。 一旦引用计数达到零,对象就不再可访问,因为访问对象需要一个引用,然后如果计数为零,就不再存在任何引用。

引用计数有一些令人愉快的特性:它易于理解和实现,结果实现是可移植的,相当快,并且与其他实现自己内存处理方案的库良好互动。引用计数的主要问题是有时它无法识别对象不再可访问,从而导致内存泄露,这发生在存在引用循环时。

考虑最简单的循环,一个类实例引用自身:

  1. instance = SomeClass()
  2. instance.myself = instance

在执行完上述两行代码后,instance 的引用计数是 2;一个引用来自名为 'instance' 的变量,另一个引用来自该实例的 myself 属性。

如果下一行代码是 del instance,会发生什么? instance 的引用计数会减少 1,所以它的引用计数变为 1;myself 属性中的引用仍然存在。 然而,该实例不能再通过 Python 代码访问,并且它可以被删除。 如果多个对象相互引用,它们可以参与一个循环,导致所有对象都无法被垃圾回收,从而导致内存泄漏。

Python 2.0 通过周期性的执行一个循环检测算法来解决这个问题,该算法查找不可访问的循环并删除涉及的对象。 一个新的 gc 模块提供了执行垃圾回收、获取调试统计信息和调整回收器参数的功能。

运行循环检测算法需要一些时间,因此会带来一些额外的开销,希望在使用 2.0 版本的循环收集经验之后,Python 2.1 可以通过精细调整来尽量减少开销。 目前还不清楚性能损失有多大,因为对此进行精准测试很棘手,而且关键在于程序创建和销毁对象的频率。 如果你不能接受哪怕是微小的速度损失,或者怀疑循环收集存在问题,可以在编译 Python 时禁用循环检测,通过在运行 configure 脚本时指定 --without-cycle-gc 开关来实现。

有几个人解决了这个问题并为解决方案做出了贡献。循环检测方法的早期实现由 Toby Kelsey 编写。 当前的算法是在 Eric Tiedemann 访问 CNRI 期间提出的,Guido van Rossum 和 Neil Schemenauer 分别编写了两个不同的实现,后来由 Neil 将它们整合。 许多其他人也在过程中提出了建议;python-dev 邮件列表 2000 年 3 月的存档包含了大部分相关讨论,尤其是在标题为 “Reference cycle collection for Python” 和 “Finalization again” 的帖子中。

其他核心变化

Python 的语法和内置函数进行了各种小改动。 虽然这些改动都不是非常深远,但它们都是很方便的改进。

细微的语言特性修改

一种新的语法是的使用元组和/或字典作为参数来调用函数更加方便。在 Python 1.5 及更早版本中,你会使用 apply() 内置函数: apply(f, args, kw) 调用函数 f(),附带参数元组 args 和关键字参数字典 kw。 在 Python 2.0 中,apply() 的使用方式是相同的。 但由于 Greg Ewing 的补丁,f(*args, **kw) 是一种更简洁明了的方式来实现相同的效果。 这种语法与定义函数的语法是对称的。

  1. def f(*args, **kw):
  2. # args is a tuple of positional args,
  3. # kw is a dictionary of keyword args
  4. ...

print 语句现在可以通过在 print 后面加上 >> file 来将其输出定向到文件型对象,这类似于 Unix shell 中的重定向操作符。 以前,你要么必须使用文件对象的 write() 方法,这缺乏 print 的方便和简单,要么你可以为 sys.stdout 分配一个新值,然后恢复旧值。 为了将输出发送到标准错误,现在可以更简单地写成这样:

  1. print >> sys.stderr, "Warning: action field not supplied"

模块现在可以在导入时重命名,使用语法 import module as namefrom module import name as othername。这个补丁是由 Thomas Wouters 提交的。

当使用 % 操作符时有一种新的格式样式可用;’%r’ 将插入其参数的 repr() 表示。 这也是为了对称性,这次是为了与现有的 %s 格式样式对称,后者插入其参数的 str() 表示。例如,'%r %s' % ('abc', 'abc') 返回一个包含 'abc' abc 的字符串。

以前没有办法实现一个类来重载 Python 的内置 in 操作符并实现自定义版本。obj in seq 返回真如果 obj 存在于序列 seq 之中;Python 通过简单地尝试索引来计算这个结果,直到找到 obj 或遇到 IndexError。 Moshe Zadka 提供了一个补丁,增加了一个 __contains__() 魔术方法,用于为 in 提供自定义实现。 此外,用 C 编写的新内置对象可以通过序列协议中的新槽定义 in 对它们的含义。

早期版本的 Python 使用递归算法来删除对象。深度嵌套的数据结构可能导致解释器填满 C 栈并崩溃。 Christian Tismer 重写了删除逻辑来解决这个问题。 相关地,比较递归对象时会导致无线递归并崩溃。Jeremy Hylton 重写了代码,使其不再崩溃,而是产生有用的结果。 例如,在以下代码之后:

  1. a = []
  2. b = []
  3. a.append(a)
  4. b.append(b)

比较 a==b 将返回真值,因为这两个递归数据结构是同构的。 请参阅 python-dev 邮件列表 2000 年 4 月的存档中的 “trashcan and PR#7” 帖子,以了解导致此实现的讨论和一些相关的有用链接。 请注意,现在的比较操作也可以引发异常。 在早期版本的 Python 中,即使用户定义的 __cmp__() 方法遇到错误,譬如 cmp(a,b) 的比较操作也总会产生一个答案,因为结果异常会被静默处理掉。

来自 ActiveState 的 Trent Mick 主要负责将 Python 移植到 Itanium 处理器上的 64 位 Windows 上。 (令人困惑的是,在 Win64 上,sys.platform 类型仍然是 'win32' 的,因为为了便于移植,MS Visual C++ 在 Itanium 上将代码视为 32 位。) PythonWin 还支持 Windows CE;更多信息请参见 https://pythonce.sourceforge.net/ 的 Python CE 页面。

另一个新的平台是 Darwin/MacOS X;Python 2.0 中提供了初步支持。如果你指定 “configure —with-dyld —with-suffix=.x”,动态加载是可行的。 有关更多说明,请参阅 Python 源代码分发中的 README 文件。

已经尝试解决 Python 的一个问题,即当代码在局部变量赋值之前引用该变量时,会引发经常令人困惑的 NameError 异常。例如,以下代码在 1.5.2 和 2.0 中都会在 print 语句上引发异常;在 1.5.2 中,会引发 NameError 异常,而在 2.0 中,会引发一个新的 UnboundLocalError 异常。 UnboundLocalErrorNameError 的子类,因此任何期望引发 NameError 的现有代码应该仍然可以正常工作。

  1. def f():
  2. print "i=",i
  3. i = i + 1
  4. f()

新引入了两个异常 TabErrorIndentationError。 它们均为 SyntaxError 的子类,并会在发现 Python 代码缩进不正确时被引发。

对于内置函数的修改

添加了一个新的内置函数 zip(seq1, seq2, ...)zip() 返回一个包含元组的列表,每个元组包含每个参数序列的第i个元素。zip()map(None, seq1, seq2) 的区别在于,如果序列长度不一致,map() 会用 None 填充序列,而 zip() 会将返回的列表截短到最短的参数序列的长度。

int()long() 函数现在在第一个参数是字符串时接受一个可选的“base”参数。int('123', 10) 返回 123,而 int('123', 16) 返回 291。int(123, 16) 会引发一个 TypeError 异常,消息为 “can’t convert non-string with explicit base”。

sys 模块中添加了一个新变量,用于保存更详细的版本信息。 sys.version_info 是一个包含五个元素的元组 (major, minor, micro, level, serial)。 例如,在假设的 2.0.1beta1 版本中,sys.version_info 将是 (2, 0, 1, 'beta', 1)level 是一个字符串,如 "alpha""beta" 或代表最终发布版本的 "final"

字典有一个特别的新方法 setdefault(key, default),其行为与现有的 get() 方法类似。 但是,如果键找不到,setdefault() 既会像 get() 一样返回 default 的值,也会将其插入字典作为 key 的值。 因此,下面的代码行:

  1. if dict.has_key( key ): return dict[key]
  2. else:
  3. dict[key] = []
  4. return dict[key]

可以简化为单个``return dict.setdefault(key, [])``语句。

解释器设置了一个最大递归深度,以便在填满 C 栈并导致核心储存或 GPF 之前捕获失控递归。以前这个限制是在编译 Python 时固定的,但在 2.0 中最大递归深度可以使用 sys.getrecursionlimit()sys.setrecursionlimit() 读取和修改。 默认值是 1000,可以通过运行一个新脚本 Misc/find_recursionlimit.py 来找到给定平台的大致的最大值。

移植 Python 2.0

新的 Python 版本尽力与之前的版本兼容,而且兼容性记录相当不错。 然而,有些变化被认为足够有用,通常是因为他们修正了最终设计中的错误决定,因此有时无法避免打破向后兼容性。 本节列出了 Python 2.0 中可能导致旧 Python 代码中断的更改。

可能会导致最多代码中断的更改是对某些方法接受的参数进行了严格限制。一些方法会接受多个参数并将它们视为一个元组,特别是各种列表方法,如 append()insert()。在早期版本的Python中,如果 L 是一个列表,L.append( 1,2 ) 会将元组 (1,2) 附加到列表中。在Python 2.0中,这会引发一个 TypeError 异常,消息为:“append requires exactly 1 argument; 2 given”。解决方法是简单地添加一组括号,将两个值作为一个元组传递:L.append( (1,2) )

这些方法的早期版本更加宽容,因为它们使用了 Python C 接口中的一个旧函数来解析它们的参数;2.0 版本将它们现代化,使用 PyArg_ParseTuple(),这是当前的参数解析函数,它提供了更有用的错误消息,并将多参数调用视为错误。如果你必须使用 2.0 但无法修复你的代码,可以编辑 Objects/listobject.c 并定义预处理符号 NO_STRICT_LIST_APPEND 以保留旧的行为;但这并不推荐。

socket 模块中的某些函数仍然是宽容的。 例如,socket.connect( ('hostname', 25) ) 是正确的形式,传递一个表示 IP 地址的元组,但 socket.connect('hostname', 25) 也可以工作。 socket.connect_exsocket.bind 也是类似的宽松方式。 2.0alpha1 会更严格地检查这些函数,但是由于文档实际上使用了错误的多参数形式,许多人编写的代码在更严格的检查下会出错。 面对公众的反应 GvR 撤销了这些理性,因此对于 socket 模块,文档已被修正,多参数形式只是被标记为已弃用;在未来的 Python 版本中它 将会 再次变得严格。

字符串字面量中的 \x 转义现在必须精确地使用2个十六进制数字。之前它会消耗 x 后面的所有十六进制数字,并取结果的最低8位,所以 \x123456 等同于 \x56

AttributeErrorNameError 异常现在有了更友好的错误消息,其文本内容类似于 'Spam' instance has no attribute 'eggs'name 'eggs' is not defined。 之前的错误消息只是缺少的属性名称,如 eggs,因此利用这一事实编写的代码在 2.0 中会中断。

在 Python 2.0 中,做了一些工作使得整数和长整数更加可互换。 在 1.5.2 中,为 Solaris 添加了大文件支持,允许读取大于 2 GiB 的文件;这使得文件对象的 tell() 方法返回长整数而不是常规整数。 一些代码会减去两个文件偏移量,并尝试使用结果来乘以一个序列或切片一个字符串,但这会引发 TypeError。 在 2.0 中,长整数可以用于乘以或切片一个序列,并且会按直觉行为;例如,3L * 'abc'‘ 生成 ‘abcabcabc’,(0,1,2,3)[2L:4L] 生成 (2,3)。 长整数也可以在以前只接受整数的各种上下文中使用,例如文件对象的 seek() 方法,以及 % 操作符支持的格式(如 %d%i%x 等)。 例如,"%d" % 2L**64 将生成字符串 18446744073709551616

最微妙的长整数变化是,长整数的 str() 表示不再有尾随的 ‘L’ 字符,尽管 repr() 表示仍然包含它。许多人在打印长整数时不希望看到 ‘L’ 字符,因为他们不得不专门去掉这个字符。在2.0中,这不再是一个问题,但那些使用 str(longval)[:-1] 并假设存在 ‘L’ 的代码,现在将丢失最后一个数字。

对浮点数执行 repr() 现在会使用不同的格式化精度,而不是 str()repr() 使用 %.17g 格式字符串来调用 C 的 sprintf(),而 str() 仍然使用 %.12g。其效果是,对于某些数字,repr() 可能比 str() 显示更多的小数位。 例如,数字 8.1 无法精确地用二进制表示,所以 repr(8.1)'8.0999999999999996',而 str(8.1) 是 '8.1'

-X 命令行选项已被移除,该选项会将所有标准异常转换为字符串而不是类;现在标准异常将始终是类。包含标准异常的 exceptions 模块已从Python翻译为内置C模块,由Barry Warsaw和Fredrik Lundh编写。

扩展/嵌入更改

有些更改是在底层进行的,仅对编写 C 扩展模块或在更大的应用中嵌入 Python 解释器的人有价值。 如果你不处理 Python 的 C API,可以安全地跳过这一节。

The version number of the Python C API was incremented, so C extensions compiled for 1.5.2 must be recompiled in order to work with 2.0. On Windows, it’s not possible for Python 2.0 to import a third party extension built for Python 1.5.x due to how Windows DLLs work, so Python will raise an exception and the import will fail.

使用 Jim Fulton 的 ExtensionClass 模块的用户将很高兴地发现,已经添加了钩子以支持 ExtensionClasses,因此现在支持 isinstance()issubclass()。 这意味着你不再需要记住编写类似 if type(obj) == myExtensionClass 这样的代码,而可以使用更自然的 if isinstance(obj, myExtensionClass)

Python/importdl.c 文件,它充满了用于支持在许多不同平台上动态加载的 #ifdef,已被 Greg Stein 清理和重组。现在 importdl.c 非常小,平台特定的代码已被移入一组特定的 Python/dynload_*.c 文件中。 另一个清理工作是:Include/ 目录中有许多包含各种可移植性修改的 my*.h 文件;它们已被合并到一个文件中,即 Include/pyport.h

Vladimir Marangozov 期待已久的 malloc 重组已经完成,使得Python解释器可以轻松使用自定义分配器,而不是C的标准 malloc()。 有关文档,请阅读 Include/pymem.hInclude/objimpl.h 中的注释。 有关界面敲定期间的详细讨论,请参阅 python.org 上的 ‘patches’ 和 ‘python-dev’ 列表的网络存档。

最新版本的 MacOS GUSI 开发环境支持 POSIX 线程。 因此,现在 Python 的 POSIX 线程支持在 Macintosh 上也可以使用。 还贡献了使用用户空间 GNU pth 库的线程支持。

Windows 上的线程支持也得到了增强。 Windows 支持的线程锁在发生争用时才使用内核对象;在常见的没有争用的情况下,他们使用简单得多的函数,这些函数快一个数量级。Python 1.5.2 在 NT 上的线程版本比无线程版本慢两倍;有了 2.0 的改进,差异仅为 10%。 这些改进由 Yakov Markovitch 提供。

Python 2.0 的源代码目前只用 ANSI C 原型,所以现在编译 Python 需要一个 ANSI C 的编译器,而不能通过仅使用支持 K&R C 的编译器完成。

之前,Python 虚拟机在其字节码中使用 16 位数字,限制了源文件的大小。 特别是,这影响了 Python 源代码中字面量列表和字典的最大大小;偶尔会有人在生成 Python 代码时遇到这个限制。 Charles G. Waldman 的补丁将这个限制从 2**16 提高到 2**32

添加了三个新的便捷函数,旨在模块初始化时将常量添加到模块的字典中: PyModule_AddObject()PyModule_AddIntConstant()PyModule_AddStringConstant()。 每个函数都接收一个模块对象、一个以空字符结尾的包含要添加的名称的C字符串,以及一个第三个参数用于指定要赋值的值。 第三个参数分别是一个 Python 对象、一个 C 长整型或一个 C 字符串。

为 Unix 风格的信号处理程序添加了一个包装API。 PyOS_getsig() 获取信号处理程序,PyOS_setsig() 设置新的处理程序。

Distutils:使模块易于安装

在 Python 2.0 之前,安装模块是一件繁琐的事情 —— 没有办法自动确定 Python 的安装位置,或者用于扩展模块的编译器选项。 软件作者不得不经历一套繁琐的程序来编辑 Makefile 和配置文件,这些只在 Unix 上真正有效,而 Windows 和 Mac OS 不受支持。 Python 用户面对不同扩展包之间大相径庭的安装说明,这使得管理 Python 成了一件麻烦事。

由 Greg Ward 领导的 SIG 创建了 distutils,一个使包安装更加容易的系统。它们构成了 distutils 包,这是 Python 标准库的新部分。 在最佳情况下,从源代码安装 Python 模块只需要以下几个步骤:首先解压缩 tarball 或 zip 归档文件,然后运行 python setup.py install。 平台会自动检测,编译器会被识别,C 扩展模块会被编译,并且分发包会安装到适当的目录中。 可选的命令行参数提供了对安装过程的更多控制,distutils 包提供了许多地方来覆盖默认设置 —— 将构建与安装分开,在非默认目录中构建或安装,等等。

为了使用 Distutils,你需要编写一个 setup.py 脚本。 在简单场景下,当软件仅包含 .py 文件时,最小化的 setup.py 可以只有几行代码:

  1. from distutils.core import setup
  2. setup (name = "foo", version = "1.0",
  3. py_modules = ["module1", "module2"])

如果软件是由几个包组成的 setup.py 文件也不会太过复杂:

  1. from distutils.core import setup
  2. setup (name = "foo", version = "1.0",
  3. packages = ["package", "package.subpackage"])

最复杂的情况可能是 C 扩展;下面是一个来自 PyXML 包的示例:

  1. from distutils.core import setup, Extension
  2. expat_extension = Extension('xml.parsers.pyexpat',
  3. define_macros = [('XML_NS', None)],
  4. include_dirs = [ 'extensions/expat/xmltok',
  5. 'extensions/expat/xmlparse' ],
  6. sources = [ 'extensions/pyexpat.c',
  7. 'extensions/expat/xmltok/xmltok.c',
  8. 'extensions/expat/xmltok/xmlrole.c', ]
  9. )
  10. setup (name = "PyXML", version = "0.5.4",
  11. ext_modules =[ expat_extension ] )

Distutils 还可以负责创建源代码和二进制分发包。运行 python setup.py sdist 的 “sdist” 命令构建一个源代码分发包,如 foo-1.0.tar.gz。添加新命令并不困难,已经有 “bdist_rpm” 和 “bdist_wininst” 命令,分别用于创建软件的 RPM 分发包和 Windows 安装程序。创建其他分发格式的命令,如 Debian 包和 Solaris .pkg 文件,也在开发的不同阶段。

所有这些都记录在一个新手册中,Distributing Python Modules,它加入了Python文档的基本集合中。

XML 模块

Python 1.5.2 包含了一个简单的 XML 解析器,以 xmllib 模块的形式提供,由 Sjoerd Mullender 贡献。自1.5.2发布以来,处理 XML 的两种不同接口已经变得常见:SAX2(Simple API for XML 的第2版)提供了一个事件驱动的接口,与 xmllib 有一些相似之处;而 DOM(Document Object Model)提供了一个基于树的接口,将 XML 文档转换为一个可遍历和修改的节点树。Python 2.0 包含了 SAX2 接口和简化的 DOM 接口,作为 xml 包的一部分。下面是这些新接口的简要概述;完整的详细信息请参阅 Python 文档或源代码。Python XML SIG 也在致力于改进文档。

SAX2 支持

SAX 定义了一个事件驱动的接口来解析 XML。要使用 SAX,你必须编写一个 SAX 处理器类。处理器类继承自 SAX 提供的各种类,并覆盖各种方法,这些方法会在 XML 解析器遇到相应事件时被调用。例如,startElement()endElement() 方法会在解析器遇到每个开始和结束标签时被调用,characters() 方法会在解析器遇到每个字符数据块时被调用,等等。

事件驱动方法的优点是整个文档不必同时驻留在内存中,这在处理非常大的文档时尤其重要。然而,如果你试图以某种复杂的方式修改文档结构,编写 SAX 处理程序类可能会变得非常复杂。

例如,这个小示例程序定义了一个处理器,它为每个开始和结束标签打印一条消息,然后使用它来解析文件 hamlet.xml:

  1. from xml import sax
  2. class SimpleHandler(sax.ContentHandler):
  3. def startElement(self, name, attrs):
  4. print 'Start of element:', name, attrs.keys()
  5. def endElement(self, name):
  6. print 'End of element:', name
  7. # Create a parser object
  8. parser = sax.make_parser()
  9. # Tell it what handler to use
  10. handler = SimpleHandler()
  11. parser.setContentHandler( handler )
  12. # Parse a file!
  13. parser.parse( 'hamlet.xml' )

欲了解更多信息,请查阅 Python 文档,或 https://pyxml.sourceforge.net/topics/howto/xml-howto.html 上的 XML HOWTO。

DOM 支持

文档对象模型是一种基于树的表示法,用于表示 XML 文档。一个顶级 Document 实例是树的根节点,并且有一个子节点,它是顶级 Element`实例。这个 :class:!Element` 具有表示字符数据和任何子元素的子节点,这些子节点可以有进一步的子节点,依此类推。使用 DOM,你可以以任何方式遍历生成的树,访问元素和属性值,插入和删除节点,并将树转换回 XML。

DOM 在修改 XML 文档方面非常有用,因为你可以创建一个 DOM 树,通过添加新节点或重新排列子树来修改它,然后生成一个新的 XML 文档作为输出。你还可以手动构建一个 DOM 树并将其转换为 XML,这比简单地将``<tag1>``…``</tag1>``写入文件更灵活。

Python 附带的 DOM 实现在 xml.dom.minidom 模块中。它是一个轻量级的 Level 1 DOM 实现,支持 XML 命名空间。提供了 parse()parseString() 便捷函数用于生成 DOM 树:

  1. from xml.dom import minidom
  2. doc = minidom.parse('hamlet.xml')

doc 是一个 Document 实例。Document,和其他所有 DOM 类如:class:!Element 和 Text`一样,都是:class:!Node` 基类的子类。因此,DOM 树中的所有节点都支持某些通用方法,例如 toxml() 方法,该方法返回一个包含节点及其子节点的 XML 表示的字符串。每个类也有自己的特定方法;例如,ElementDocument 实例有一个方法可以找到具有给定标签名的所有子元素。继续前面 2 行代码的例子:

  1. perslist = doc.getElementsByTagName( 'PERSONA' )
  2. print perslist[0].toxml()
  3. print perslist[1].toxml()

对于 Hamlet XML 文件,上面几行代码输出:

  1. <PERSONA>CLAUDIUS, king of Denmark. </PERSONA>
  2. <PERSONA>HAMLET, son to the late, and nephew to the present king.</PERSONA>

文件的根元素可以通过 doc.documentElement 访问,并且可以通过删除、添加或一处节点来听松修改节点来轻松修改其子元素:

  1. root = doc.documentElement
  2. # Remove the first child
  3. root.removeChild( root.childNodes[0] )
  4. # Move the new first child to the end
  5. root.appendChild( root.childNodes[0] )
  6. # Insert the new first child (originally,
  7. # the third child) before the 20th child.
  8. root.insertBefore( root.childNodes[0], root.childNodes[20] )

再次,我建议你查阅 Python 文档,以获取不同 Node 类及各种方法的完整列表。

与 PyXML 的关系

XML 特别兴趣小组已经致力于与 XML 相关的 Python 代码有一段时间了。 其代码分发包称为 PyXML,可以从 SIG 的网页 https://www.python.org/community/sigs/current/xml-sig 获取。 PyXML 分发包也使用了 xml 作为包名。 如果你编写了使用 PyXML 的程序,可能会担心它与 Python 2.0 的 xml 包的兼容性。

答案是 Python 2.0 的 xml 包与 PyXML 不兼容,但可以通过安装最新版本的 PyXML 来使其兼容。 许多应用程序可以依赖 Python 2.0 中包含的 XML 支持,但更复杂的应用程序需要安装完整的 PyXML 包。 安装后,版本为 0.6.0 或更高的 PyXML 将替换随 Python 一起发布的 xml 包,并且将是标准包的严格超集,添加许多额外的功能。 PyXML 中的一些附加功能包括:

  • 4DOM,一个来自 FourThought, Inc. 的完整 DOM 实现。

  • xmlproc 验证解析器,由 Lars Marius Garshol 编写。

  • sgmlop 解析器加速模块,由 Fredrik Lundh 编写。

模块更改

对 Python 庞大的标准库进行了大量改进和错误修复;一些受影响的模块包括 readline, ConfigParser, cgi, calendar, posix, readline, xmllib, aifc, chunk, wave, random, shelvenntplib。 请参阅 CVS 日志获取具体的逐个补丁详情。

Brian Gallew 贡献了 socket 模块的 OpenSSL 支持。OpenSSL 是一个安全套接字层实现,用于加密通过套接字发送的数据。在编译 Python 时,你可以编辑 Modules/Setup 文件以包含 SSL 支持,这会为 socket 模块添加一个额外的函数:socket.ssl(socket, keyfile, certfile),它接收一个套接字对象并返回一个 SSL 套接字。httpliburllib 模块也进行了更改,以支持``https://\`\` 的URL,但目前还没有人实现通过 SSL 的 FTP 或 SMTP。

httplib 模块已由 Greg Stein 重写以支持 HTTP/1.1。

对 1.5 版本的 httplib 提供了向下兼容性,尽管使用 HTTP/1.1 特性如流水线将需要重写代码以使用不同的一组接口。

Tkinter 模块现在支持 Tcl/Tk 版本 8.1、8.2 或 8.3,并且不再支持旧的 7.x 版本。Tkinter 模块现在支持在 Tk 小部件中显示 Unicode 字符串。此外,Fredrik Lundh 贡献了一项优化,使得诸如 create_linecreate_polygon 等操作速度更快,尤其是在使用大量坐标时。

curses 模块在 Oliver Andrich 增强版本的基础上得到了极大的扩展,提供了许多来自 ncurses 和 SYSV curses 的附加功能,如颜色、替代字符集支持、手写板和鼠标支持等。 这意味着该模块不再兼容仅有 BSD curses 的操作系统,但目前似乎没有任何属于这一类别的操作系统正在维护中。

如前面讨论的 Python 2.0 对 Unicode 的支持所提到的,re 模块提供的正则表达式的底层实现已经更改。 SRE,一个新的正则表达式引擎,由 Ferdrik Lundh 编写,部分由惠普资助,支持匹配 8 位字符串和 Unicode 字符串。

新增模块

添加了许多新模块,我们将简单列出它们并附上简要概述;有关特定模块的详细信息,请查阅 2.0 文档。

  • atexit 模块用于注册在 Python 解释器退出之前调用的函数。当前直接设置 sys.exitfunc 的代码应改为使用 atexit 模块,导入 atexit 并使用 atexit.register() 调用要在退出时调用的函数。 (由 Skip Montanaro 贡献。)

  • codecs, encodings, unicodedata: 作为新的Unicode支持的一部分添加。

  • filecmp 模块取代了旧的 cmp, cmpcache, 和 dircmp 模块,这些模块现在已经被废弃。(由 Gordon MacMillan 和 Moshe Zadka 贡献。)

  • gettext: 这个模块通过提供 GNU gettext 消息目录库的接口,为 Python 程序提供国际化(I18N)和本地化(L10N)支持。 (由 Barry Warsaw 整合,来自 Martin von Löwis, Peter Funk 和 James Henstridge 的独立贡献。)

  • linuxaudiodev: 支持 Linux 上的 /dev/audio 设备,与现有的 sunaudiodev 模块相对应。(由 Peter Bosch 贡献,Jeremy Hylton 提供修复。)

  • mmap: 提供对内存映射文件的接口,支持 Windows 和 Unix。文件的内容可以直接映射到内存中,此时它表现得像一个可变字符串,因此可以读取和修改其内容。它们甚至可以传递给期望普通字符串的函数,例如 re 模块。(由 Sam Rushing 贡献,并由 A.M. Kuchling 提供一些扩展。)

  • pyexpat: Expat XML解析器的接口。 (由 Paul Prescod 贡献。)

  • robotparser: 解析 robots.txt 文件,这个文件用于编写礼貌地避开网站某些区域的网络爬虫。 解析器接受 robots.txt 文件的内容,从中构建一组规则,然后可以回答有关给定 URL 是否可以被抓取的问题。 (由 Skip Montanaro 贡献。)

  • tabnanny: 一个模块/脚本,用于检查 Python 源代码中不歧义的缩进。 (由 Tim Peters 贡献。)

  • UserString: 一个基类,用于派生行为类似于字符串的对象。

  • webbrowser 模块提供了一种平台独立的方式来在特定 URL 上启动 web 浏览器。对于每个平台,模块会按特定顺序尝试各种浏览器。用户可以通过设置 BROWSER 环境变量来更改启动的浏览器。(最初的灵感来自 Eric S. Raymond 对 urllib 的补丁,该补丁增加了类似的功能,但最终的模块来自 Fred Drake 最初实现的代码 Tools/idle/BrowserControl.py,并由 Fred 适配到标准库中。)

  • _winreg 模块:提供了一个用于访问 Windows 注册表的接口。_winreg 是对自 1995 年以来一直是 PythonWin 一部分的函数的改编,现在已被添加到核心分发版中,并增强了对 Unicode 的支持。 _winreg 由 Bill Tutt 和 Mark Hammond 编写。

  • zipfile 模块:用于读取和写入 ZIP 格式的归档文件。 这些归档文件是由 DOS/Windows 上的 PKZIP 或 Unix 上的 zip 生成的,不要与 gzip 格式文件(由 gzip 支持)混淆。 (由 James C. Ahlstrom 贡献。)

  • imputil`模块:提供了一种比现有的 :mod:!ihooks` 模块更简单的方式来编写自定义导入钩子。(由 Greg Stein 实现,并在实现过程中在 python-dev 进行了大量讨论。)

IDLE 改进

IDLE 是官方的 Python 跨平台 IDE,使用 Tkinter 编写。 Python 2.0 包括了 IDLE 0.6,它增加了许多新特性和改进。 部分新内容列表:

  • 界面改进和优化,特别是在语法高亮和自动缩进方面。

  • 类浏览器现在将显示更多信息,例如一个模块中最高层级的函数。

  • 制表符宽度现在是一个用户可设置的选项。在打开现有的 Python 文件时,IDLE 会自动检测缩进约定并进行适应。

  • 现在支持在各种平台上调用浏览器,用于在浏览器中打开 Python 文档。

  • IDLE 现在有一个命令行,它与原版 Python 解释器大致相同。

  • 在许多地方添加了调用提示。

  • IDLE 现在可以作为一个包被安装。

  • 在编辑器窗口中,目前在底部位置增加了一个行/列显示栏。

  • 三个新的键盘快捷键:检查模块 (Alt-F5),导入模块 (F5) 和执行脚本 (Ctrl-F5)。

删除和弃用的模块

有几个模块因为已经过时,或者因为现在有更好的方法来实现相同的功能而被删除。 stdwin 模块已被移除;它是一个不再开发的跨平台窗口工具包。

许多模块已经移动到了 lib-old 子目录: cmpcmpcachedircmpdumpfindgreppackmailpolyutilwhatsoundzmod。 如果你的代码依赖于已移动到 lib-old 的模块,你可以简单地将该目录添加到 sys.path 中来找回它们,但建议你更新任何使用这些模块的代码。

致谢

作者感谢以下人士对本文的各种草稿提出建议: David Bolen, Mark Hammond, Gregg Hauser, Jeremy Hylton, Fredrik Lundh, Detlef Lannert, Aahz Maruch, Skip Montanaro, Vladimir Marangozov, Tobias Polzin, Guido van Rossum, Neil Schemenauer, and Russ Schmidt.