Python 2.0 的新增功能 — Python 文档

来自菜鸟教程
Python/docs/3.7/whatsnew/2.0
跳转至:导航、​搜索

Python 2.0 中的新增功能

作者
是 库克林和摩西扎德卡

介绍

2000 年 10 月 16 日发布了 Python 的新版本 2.0。 本文介绍了 2.0 中令人兴奋的新功能,重点介绍了一些其他有用的更改,并指出了一些可能需要重写代码的不兼容更改。

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 的错误或提交补丁,请使用 Python 项目页面中提供的错误跟踪和补丁管理器工具,该页面位于 https://sourceforge.net/projects/python/。

现在托管在 SourceForge 的最重要的服务是 Python CVS 树,它是包含 Python 源代码的版本控制存储库。 以前,大约有 7 个左右的人对 CVS 树有写访问权限,所有补丁都必须由这个短名单中的一个人检查和签入。 显然,这不是很可扩展。 通过将 CVS 树移动到 SourceForge,可以向更多人授予写访问权限; 截至 2000 年 9 月,有 27 人能够检查变更,增加了四倍。 这使得大规模更改成为可能,如果必须通过一小组核心开发人员进行过滤,则不会尝试这些更改。 例如,有一天 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 Enhancement Proposals (PEP),以 Internet RFC 流程为模型。 PEP 是描述提议的新功能的草稿文件,并且会不断修订,直到社区达成共识,接受或拒绝该提案。 引自 PEP 1 的介绍,“PEP 目的和指南”:

PEP 代表 Python 增强提案。 PEP 是为 Python 社区提供信息或描述 Python 新功能的设计文档。 PEP 应提供该功能的简明技术规范和该功能的基本原理。

我们希望 PEP 成为提出新功能、收集社区对某个问题的意见以及记录已进入 Python 的设计决策的主要机制。 PEP 作者负责在社区内建立共识并记录不同意见。


阅读 PEP 1 的其余部分,了解 PEP 编辑过程、风格和格式的详细信息。 PEP 保存在 SourceForge 上的 Python CVS 树中,尽管它们不是 Python 2.0 发行版的一部分,也可以从 https://www.python.org/dev/peps/[X186X 以 HTML 形式获得]。 截至 2000 年 9 月,有 25 个 PEPS,从 PEP 201,“锁步迭代”到 PEP 225,“元素/对象操作符”。


统一码

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"。 可以使用新的转义序列 \uHHHH 写入任意 Unicode 字符,其中 HHHH 是从 0000 到 FFFF 的 4 位十六进制数。 也可以使用现有的 \xHHHH 转义序列,对于 U+01FF 以内的字符可以使用八进制转义,用 \777 表示。

Unicode 字符串,就像普通字符串一样,是一种不可变的序列类型。 它们可以被索引和切片,但不能就地修改。 Unicode 字符串有一个 encode( [encoding] ) 方法,该方法返回所需编码的 8 位字符串。 编码由字符串命名,例如 'ascii''utf-8''iso-8859-1' 或其他任何内容。 编解码器 API 被定义用于实现和注册新的编码,然后在整个 Python 程序中可用。 如果未指定编码,则默认编码通常为 7 位 ASCII,但可以通过在 site.py 的自定义版本中调用 sys.setdefaultencoding(encoding) 函数来更改 Python 安装。

结合 8 位和 Unicode 字符串总是强制转换为 Unicode,使用默认的 ASCII 编码; '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 是一个 8 位字符串,其中包含一部分(可能是全部)Unicode 字符串转换为给定的编码,而 length 告诉您 Unicode 字符串的转换量。
  • decode_funcencode_func 相反,取一个 8 位字符串并返回一个 2 元组 (ustring, length),由生成的 Unicode 字符串 ustring 组成] 和整数 length 表示消耗了多少 8 位字符串。
  • stream_reader 是一个支持从流中解码输入的类。 stream_reader(file_obj) 返回一个支持 read()readline()readlines() 方法的对象。 这些方法都将从给定的编码转换并返回 Unicode 字符串。
  • stream_writer 同样是支持编码输出到流的类。 stream_writer(file_obj) 返回一个支持 write()writelines() 方法的对象。 这些方法需要 Unicode 字符串,在输出时将它们转换为给定的编码。

例如,以下代码将 Unicode 字符串写入文件,并将其编码为 UTF-8:

import codecs

unistr = u'\u0660\u2000ab ...'

(UTF8_encode, UTF8_decode,
 UTF8_streamreader, UTF8_streamwriter) = codecs.lookup('UTF-8')

output = UTF8_streamwriter( open( '/tmp/output', 'wb') )
output.write( unistr )
output.close()

然后,以下代码将从文件中读取 UTF-8 输入:

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

Unicode 感知正则表达式可通过 re 模块获得,该模块有一个名为 SRE 的新底层实现,由 Secret Labs AB 的 Fredrik Lundh 编写。

添加了 -U 命令行选项,这会导致 Python 编译器将所有字符串文字解释为 Unicode 字符串文字。 这旨在用于测试和面向未来的 Python 代码,因为 Python 的某些未来版本可能会放弃对 8 位字符串的支持并仅提供 Unicode 字符串。


列表推导式

列表是 Python 中的主力数据类型,许多程序在某些时候操作列表。 列表上的两种常见操作是遍历它们,或者挑选出满足特定条件的元素,或者对每个元素应用一些函数。 例如,给定一个字符串列表,您可能希望提取包含给定子字符串的所有字符串,或者从每行中去除尾随空格。

现有的 map()filter() 函数可用于此目的,但它们需要一个函数作为其参数之一。 如果存在可以直接传递的现有内置函数,这很好,但如果没有,则必须创建一个小函数来完成所需的工作,如果小函数需要,Python 的作用域规则会使结果变得丑陋附加信息。 以上一段中的第一个示例为例,查找列表中包含给定子字符串的所有字符串。 您可以编写以下内容来做到这一点:

# Given the list L, make a list of all strings
# containing the substring S.
sublist = filter( lambda s, substring=S:
                     string.find(s, substring) != -1,
                  L)

由于 Python 的作用域规则,使用默认参数以便由 lambda 表达式创建的匿名函数知道正在搜索什么子字符串。 列表推导式使这更清晰:

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

列表推导式具有以下形式:

[ expression for expr in sequence1
             for expr2 in sequence2 ...
             for exprN in sequenceN
             if condition ]

forin 子句包含要迭代的序列。 序列的长度不必相同,因为它们是 不是 并行迭代,而是从左到右; 下文将对此进行更清楚的解释。 生成列表的元素将是 expression 的连续值。 最后的 if 子句是可选的; 如果存在,则 expression 仅在 condition 为真时才计算并添加到结果中。

为了使语义非常清晰,列表推导等价于以下 Python 代码:

for expr1 in sequence1:
    for expr2 in sequence2:
    ...
        for exprN in sequenceN:
             if (condition):
                  # Append the value of
                  # the expression to the
                  # resulting list.

这意味着当有多个 forin 子句时,结果列表将等于所有序列长度的乘积。 如果您有两个长度为 3 的列表,则输出列表的长度为 9 个元素:

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

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

# Syntax error
[ x,y for x in seq1 for y in seq2]
# Correct
[ (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 类存储一个数字并支持使用 += 创建一个具有递增值的新实例。

class Number:
    def __init__(self, value):
        self.value = value
    def __iadd__(self, increment):
        return Number( self.value + increment)

n = Number(5)
n += 3
print n.value

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

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


字符串方法

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

相反,Python 2.0 将问题推到了字符串类型上,通过 8 位字符串和 Unicode 字符串上的方法使字符串操作功能可用。

>>> 'andrew'.capitalize()
'Andrew'
>>> 'hostname'.replace('os', 'linux')
'hlinuxtname'
>>> 'moshe'.find('sh')
2

没有改变的一件事是 Python 字符串是不可变的,尽管这是一个值得注意的愚人节玩笑。 因此,字符串方法返回新字符串,并且不修改它们操作的字符串。

为了向后兼容,旧的 string 模块仍然存在,但它主要充当新字符串方法的前端。

在 2.0 之前的版本中没有并行的两种方法,尽管它们在 JPython 中确实存在了很长一段时间,但它们是 startswith()endswith()s.startswith(t)相当于s[:len(t)] == ts.endswith(t)相当于s[-len(t):] == t

另一种值得特别提及的方法是 join()。 字符串的 join() 方法接收一个参数,一个字符串序列,相当于旧的 string 模块中的 string.join() 函数,只是参数颠倒了。 换句话说,s.join(seq) 相当于旧的 string.join(seq, s)


循环的垃圾收集

Python 的 C 实现使用引用计数来实现垃圾收集。 每个 Python 对象都维护指向自身的引用数量的计数,并在创建或销毁引用时调整计数。 一旦引用计数达到零,该对象将不再可访问,因为您需要有一个对对象的引用才能访问它,如果计数为零,则不再存在引用。

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

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

instance = SomeClass()
instance.myself = instance

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

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

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

运行循环检测算法需要一些时间,因此会导致一些额外的开销。 希望在我们从使用 2.0 获得循环收集经验后,Python 2.1 能够通过仔细调整将开销降至最低。 目前还不清楚损失了多少性能,因为对此进行基准测试很棘手,并且关键取决于程序创建和销毁对象的频率。 编译 Python 时可以禁用循环检测,如果您无法承受哪怕是微小的速度损失或怀疑循环收集有问题,请通过在运行 配置时指定 --without-cycle-gc 开关[ X223X] 脚本。

有几个人解决了这个问题,并为解决方案做出了贡献。 循环检测方法的早期实现是由 Toby Kelsey 编写的。 目前的算法是 Eric Tiedemann 在访问 CNRI 时提出的,Guido van Rossum 和 Neil Schemenauer 编写了两个不同的实现,后来由 Neil 集成。 一路上还有很多其他人提出了建议; python-dev 邮件列表的 2000 年 3 月档案包含大部分相关讨论,特别是在标题为“Python 的参考周期集合”和“再次终结”的主题中。


其他核心变化

对 Python 的语法和内置函数进行了各种细微的更改。 没有任何变化影响深远,但它们非常方便。

轻微的语言变化

新的语法使得使用参数元组和/或关键字参数字典调用给定函数变得更加方便。 在 Python 1.5 及更早版本中,您将使用 apply() 内置函数:apply(f, args, kw) 使用参数元组 args 调用函数 f() 和字典 kw 中的关键字参数。 apply() 在 2.0 中是相同的,但多亏了 Greg Ewing 的补丁,f(*args, **kw) 是实现相同效果的更短、更清晰的方法。 此语法与定义函数的语法是对称的:

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

print 语句现在可以通过跟随 print>> file 将其输出定向到类似文件的对象,类似于 Unix shell 中的重定向运算符。 以前,您要么必须使用类文件对象的 write() 方法,该方法缺乏 print 的便利性和简单性,要么您可以为 sys.stdout 分配一个新值] 然后恢复旧值。 为了将输出发送到标准错误,这样写要容易得多:

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 出现在序列 seq 中,obj in seq 返回真; Python 通过简单地尝试序列的每个索引来计算它,直到找到 obj 或遇到 IndexError。 Moshe Zadka 贡献了一个补丁,它添加了一个 __contains__() 魔法方法,用于为 in 提供自定义实现。 此外,用 C 编写的新内置对象可以通过序列协议中的新插槽定义 in 对它们的意义。

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

a = []
b = []
a.append(a)
b.append(b)

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

已经完成了将 Python 移植到安腾处理器上的 64 位 Windows 的工作,主要是由 ActiveState 的 Trent Mick 完成的。 (令人困惑的是,sys.platform 在 Win64 上仍然是 'win32',因为似乎为了便于移植,MS Visual C++ 在 Itanium 上将代码视为 32 位。)PythonWin 也支持 Windows CE; 有关更多信息,请参阅 http://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 的现有代码都应该仍然有效。

def f():
    print "i=",i
    i = i + 1
f()

引入了两个新异常,TabErrorIndentationError。 它们都是 SyntaxError 的子类,并且在发现 Python 代码缩进不当时引发。


对内置函数的更改

添加了一个新的内置函数 zip(seq1, seq2, ...)zip() 返回一个元组列表,其中每个元组包含来自每个参数序列的第 i 个元素。 zip()map(None, seq1, seq2) 之间的区别在于 map()None 填充序列,如果序列不完全相同长度,而 zip() 将返回的列表截断为最短参数序列的长度。

当第一个参数是字符串时,int()long() 函数现在接受一个可选的“基本”参数。 int('123', 10) 返回 123,而 int('123', 16) 返回 291。 int(123, 16) 引发 TypeError 异常,消息“无法转换具有显式基数的非字符串”。

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() 都返回 default 的值,如 get() 所做的那样,并将其作为 的值插入到字典中键。 因此,以下代码行:

if dict.has_key( key ): return dict[key]
else:
    dict[key] = []
    return dict[key]

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

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


移植到 2.0

新的 Python 版本努力与以前的版本兼容,并且记录非常好。 然而,一些更改被认为足够有用,通常是因为它们修复了被证明是积极错误的初始设计决策,无法始终避免破坏向后兼容性。 本节列出了 Python 2.0 中可能导致旧 Python 代码中断的更改。

可能会破坏大部分代码的更改是收紧某些方法接受的参数。 一些方法会接受多个参数并将它们视为一个元组,特别是各种列表方法,例如 append()insert()。 在早期版本的 Python 中,如果 L 是一个列表,L.append( 1,2 ) 会将元组 (1,2) 附加到列表中。 在 Python 2.0 中,这会导致引发 TypeError 异常,并显示消息:'append 需要正好 1 个参数; 2 给'。 解决方法是简单地添加一组额外的括号以将两个值作为元组传递: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_ex()socket.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 中中断。

已经做了一些工作来使整数和长整数更具可互换性。 在 1.5.2 中,为 Solaris 添加了大文件支持,以允许读取大于 2 GiB 的文件; 这使得文件对象的 tell() 方法返回一个长整数而不是常规整数。 一些代码会减去两个文件偏移量并尝试使用结果来乘以一个序列或切片一个字符串,但这引发了 TypeError。 在 2.0 中,长整数可用于对序列进行乘法或切片,它的行为与您直觉上期望的一样; 3L * 'abc' 产生 'abcabcabc',而 (0,1,2,3)[2L:4L] 产生 (2,3)。 长整数还可以用于以前只接受整数的各种上下文中,例如在文件对象的 seek() 方法中,以及在 % 运算符 ([ X206X]、%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,则可以安全地跳过本节。

Python C API 的版本号增加了,因此必须重新编译为 1.5.2 编译的 C 扩展才能使用 2.0。 在 Windows 上,由于 Windows DLL 的工作方式,Python 2.0 无法导入为 Python 1.5.x 构建的第三方扩展,因此 Python 将引发异常并且导入将失败。

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

Python/importdl.c 文件是一大堆#ifdefs,用于支持许多不同平台上的动态加载,由 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 上的“补丁”和“python-dev”列表的 Web 档案。

MacOS 的 GUSI 开发环境的最新版本支持 POSIX 线程。 因此,Python 的 POSIX 线程支持现在适用于 Macintosh。 还提供了使用用户空间 GNU pth 库的线程支持。

Windows 上的线程支持也得到了增强。 Windows 支持仅在争用情况下使用内核对象的线程锁; 在没有争用的常见情况下,他们使用更简单的函数,速度快一个数量级。 NT 上 Python 1.5.2 的线程版本比非线程版本慢两倍; 随着 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 long 或 C 字符串。

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


Distutils:使模块易于安装

在 Python 2.0 之前,安装模块是一件乏味的事情——没有办法自动找出 Python 的安装位置,或者用于扩展模块的编译器选项。 软件作者必须经历编辑 Makefile 和配置文件的艰巨过程,这些文件只在 Unix 上真正有效,而 Windows 和 MacOS 不受支持。 Python 用户面临着截然不同的安装说明,这些说明因不同的扩展包而异,这使得管理 Python 安装变得很麻烦。

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

为了使用 Distutils,您需要编写一个 setup.py 脚本。 对于简单的情况,当软件只包含 .py 文件时,最小的 setup.py 可以只有几行长:

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

如果软件由几个包组成,setup.py 文件并不复杂:

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

AC 扩展可能是最复杂的情况; 这是取自 PyXML 包的示例:

from distutils.core import setup, Extension

expat_extension = Extension('xml.parsers.pyexpat',
     define_macros = [('XML_NS', None)],
     include_dirs = [ 'extensions/expat/xmltok',
                      'extensions/expat/xmlparse' ],
     sources = [ 'extensions/pyexpat.c',
                 'extensions/expat/xmltok/xmltok.c',
                 'extensions/expat/xmltok/xmlrole.c', ]
       )
setup (name = "PyXML", version = "0.5.4",
       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 以 xmllib 模块的形式包含了一个简单的 XML 解析器,由 Sjoerd Mullender 贡献。 自 1.5.2 发布以来,处理 XML 的两种不同接口变得常见:SAX2(Simple API for XML 的第 2 版)提供了一个与 xmllib 有一些相似之处的事件驱动接口,以及 DOM(文档对象) 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

from xml import sax

class SimpleHandler(sax.ContentHandler):
    def startElement(self, name, attrs):
        print 'Start of element:', name, attrs.keys()

    def endElement(self, name):
        print 'End of element:', name

# Create a parser object
parser = sax.make_parser()

# Tell it what handler to use
handler = SimpleHandler()
parser.setContentHandler( handler )

# Parse a file!
parser.parse( 'hamlet.xml' )

有关更多信息,请参阅 Python 文档或 http://pyxml.sourceforge.net/topics/howto/xml-howto.html 上的 XML HOWTO。


DOM 支持

文档对象模型是 XML 文档的基于树的表示。 顶级 Document 实例是树的根,并且有一个子节点,即顶级 Element 实例。 这个 Element 有代表字符数据和任何子元素的子节点,这些子元素可能有更多的子元素,等等。 使用 DOM,您可以按您喜欢的任何方式遍历结果树,访问元素和属性值,插入和删除节点,并将树转换回 XML。

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

Python 中包含的 DOM 实现位于 xml.dom.minidom 模块中。 它是 Level 1 DOM 的轻量级实现,支持 XML 命名空间。 parse()parseString() 方便函数用于生成 DOM 树:

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

doc 是一个 Document 实例。 Document 与所有其他 DOM 类如 ElementText 一样,是 Node 基类的子类。 因此,DOM 树中的所有节点都支持某些通用方法,例如 toxml(),它返回一个包含节点及其子节点的 XML 表示的字符串。 每个类也有自己的特殊方法; 例如,ElementDocument 实例有一个方法来查找具有给定标签名称的所有子元素。 继续前面的 2 行示例:

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

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

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

文档的根元素可作为 doc.documentElement,其子元素可以通过删除、添加或删除节点轻松修改:

root = doc.documentElement

# Remove the first child
root.removeChild( root.childNodes[0] )

# Move the new first child to the end
root.appendChild( root.childNodes[0] )

# Insert the new first child (originally,
# the third child) before the 20th child.
root.insertBefore( root.childNodes[0], root.childNodes[20] )

同样,我将向您推荐 Python 文档,以获取不同 Node 类及其各种方法的完整列表。


与 PyXML 的关系

XML Special Interest Group 一直致力于与 XML 相关的 Python 代码。 其代码分布称为 PyXML,可从 SIG 的网页 https://www.python.org/community/sigs/current/xml-sig 获得。 PyXML 发行版还使用了包名 xml。 如果您编写过使用 PyXML 的程序,您可能想知道它与 2.0 xml 包的兼容性。

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

  • 4DOM,来自 FourThought, Inc. 的完整 DOM 实现。
  • xmlproc 验证解析器,由 Lars Marius Garshol 编写。
  • sgmlop 解析器加速器模块,由 Fredrik Lundh 编写。


模块变化

对 Python 广泛的标准库进行了大量改进和错误修正; 一些受影响的模块包括 readlineConfigParsercgicalendarposix、 X212X]、xmllibaifcchunk, waverandomshelve和[X29lib]tx.x] 请查阅 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。 提供了与 httplib 1.5 版本的向后兼容性,但使用 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 诅咒的附加功能,例如颜色、替代字符集支持、pads 和鼠标支持。 这意味着该模块不再与只有 BSD 诅咒的操作系统兼容,但似乎没有任何当前维护的操作系统属于这一类。

正如前面关于 2.0 的 Unicode 支持的讨论中提到的,re 模块提供的正则表达式的底层实现已经改变。 SRE 是一种新的正则表达式引擎,由 Fredrik Lundh 编写并由 Hewlett Packard 部分资助,支持匹配 8 位字符串和 Unicode 字符串。


新模块

添加了许多新模块。 我们将简单地列出它们并进行简要说明; 有关特定模块的详细信息,请参阅 2.0 文档。

  • atexit:用于在 Python 解释器退出之前注册要调用的函数。 当前直接设置 sys.exitfunc 的代码应该改为使用 atexit 模块,导入 atexit 并调用 atexit.register()退出时调用的函数。 (由跳过蒙塔纳罗提供。)
  • codecsencodingsunicodedata:作为新 Unicode 支持的一部分添加。
  • filecmp:取代旧的 cmpcmpcachedircmp 模块,这些模块现已弃用。 (由戈登麦克米兰和摩西扎德卡提供。)
  • 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 提供,AM 进行了一些扩展 库克林。)
  • pyexpat:Expat XML 解析器的接口。 (保罗·普雷斯科德供稿。)
  • robotparser:解析一个robots.txt文件,用于编写礼貌地避开网站某些区域的网络蜘蛛。 解析器接受 robots.txt 文件的内容,从中构建一组规则,然后可以回答有关给定 URL 的可获取性的问题。 (由跳过蒙塔纳罗提供。)
  • tabnanny:用于检查 Python 源代码是否存在歧义缩进的模块/脚本。 (由蒂姆·彼得斯提供。)
  • 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 格式的文件(由 [X165X ]gzip 模块)(由 James C. 阿尔斯特罗姆。)
  • imputil:与现有的 ihooks 模块相比,该模块提供了一种更简单的方法来编写自定义导入挂钩。 (由 Greg Stein 实施,在此过程中对 python-dev 进行了大量讨论。)


空闲改进

IDLE 是官方的 Python 跨平台 IDE,使用 Tkinter 编写。 Python 2.0 包括 IDLE 0.6,它添加了许多新功能和改进。 部分清单:

  • UI 改进和优化,特别是在语法高亮和自动缩进方面。
  • 类浏览器现在显示更多信息,例如模块中的顶级函数。
  • 标签宽度现在是用户可设置的选项。 打开现有 Python 文件时,IDLE 会自动检测缩进约定并进行调整。
  • 现在支持在各种平台上调用浏览器,用于在浏览器中打开 Python 文档。
  • IDLE 现在有一个命令行,它在很大程度上类似于 vanilla Python 解释器。
  • 在很多地方添加了通话提示。
  • IDLE 现在可以作为包安装。
  • 在编辑器窗口中,底部现在有一个行/列栏。
  • 三个新的按键命令:检查模块 (Alt-F5)、导入模块 (F5) 和运行脚本 (Ctrl-F5)。


删除和弃用的模块

一些模块已被删除,因为它们已经过时,或者因为现在有更好的方法来做同样的事情。 stdwin 模块不见了; 它用于不再开发的独立于平台的窗口工具包。

许多模块已移至 lib-old 子目录:cmpcmpcachedircmpdumpfind ]、greppackmailpolyutilwhatsoundzmod。 如果您的代码依赖于已移至 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 和 Russ Schmidt。