Python 2.0 的新特性¶
- 作者:
A.M. Kuchling 和 Moshe Zadka
简介¶
Python 的新版本 2.0 于 2000 年 10 月 16 日发布。本文介绍了 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 增强提案 (PEP),该流程以互联网 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://peps.pythonlang.cn/ 以 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 集成”。本文将仅介绍有关 Unicode 接口的最重要几点。
在 Python 源代码中,Unicode 字符串写为 u"string"
。可以使用新的转义序列 \uHHHH
写入任意 Unicode 字符,其中 HHHH 是从 0000 到 FFFF 的 4 位十六进制数。也可以使用现有的 \xHH
转义序列,并且可以将八进制转义用于 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 字符串始终使用默认 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_func 与 encode_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()
可通过 re
模块使用 Unicode 感知的正则表达式,该模块有一个由 Secret Labs AB 的 Fredrik Lundh 编写的名为 SRE 的新底层实现。
添加了一个 -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 ]
for
...in
子句包含要迭代的序列。这些序列的长度不必相同,因为它们不是并行迭代的,而是从左到右迭代的;这将在下面的段落中更清楚地解释。生成的列表的元素将是 expression 的连续值。最后的 if
子句是可选的;如果存在,只有当 condition 为真时,才会评估 expression 并将其添加到结果中。
为了使语义非常清晰,列表推导式等效于以下 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.
这意味着当存在多个 for
...in
子句时,生成的列表将等于所有序列的长度的乘积。如果您有两个长度为 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 个字符串参数的函数,这意味着有 8 种可能的排列组合,以及相应复杂的代码。
相反,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)] == t
,而 s.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
开关运行 configure 脚本,则可以禁用循环检测,如果您无法承受哪怕很小的速度损失,或者怀疑循环收集存在错误。
几个人解决了这个问题并为解决方案做出了贡献。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* 和字典 *kw* 中的关键字参数调用函数 f()
。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 name
或 from module import name as othername
。该补丁由 Thomas Wouters 提交。
使用 %
运算符时,可以使用一种新的格式样式;‘%r’ 将插入其参数的 repr()
。这也是出于对称性的考虑而添加的,这次是为了与现有的‘%s’格式样式对称,该样式插入其参数的 str()
。例如,'%r %s' % ('abc', 'abc')
返回一个包含 'abc' abc
的字符串。
以前,没有办法实现一个类来覆盖 Python 的内置 in
操作符并实现自定义版本。 obj in seq
如果 obj 存在于序列 seq 中则返回 true;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)
之类的比较操作也始终会产生答案,因为产生的异常会被静默地吞噬。
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
异常。 UnboundLocalError
是 NameError
的子类,因此任何期望引发 NameError
的现有代码都应该仍然有效。
def f():
print "i=",i
i = i + 1
f()
引入了两个新的异常,TabError
和 IndentationError
。 它们都是 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 的值插入到字典中。因此,以下几行代码
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 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_ex
和 socket.bind
也类似地比较宽松。2.0alpha1 收紧了这些函数,但是由于文档实际上使用了错误的多个参数形式,许多人编写的代码会在更严格的检查下崩溃。面对公众的反应,GvR 撤销了这些更改,因此对于 socket
模块,文档已修复,并且多个参数形式仅被标记为已弃用;在未来的 Python 版本中,它将会再次收紧。
字符串字面量中的 \x
转义现在需要正好 2 个十六进制数字。以前,它会消耗 ‘x’ 之后的所有十六进制数字,并取结果的最低 8 位,因此 \x123456
等同于 \x56
。
AttributeError
和 NameError
异常具有更友好的错误消息,其文本类似于 '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()
方法,以及 %
运算符(%d
、%i
、%x
等)支持的格式。例如,"%d" % 2L**64
将产生字符串 18446744073709551616
。
最微妙的长整数更改是长整数的 str()
不再有尾随的 ‘L’ 字符,尽管 repr()
仍然包含它。‘L’ 让许多想要打印看起来像普通整数的长整数的人感到恼火,因为他们必须特意去掉这个字符。在 2.0 中这不再是一个问题,但是执行 str(longval)[:-1]
并假设 ‘L’ 在那里的代码现在将丢失最后一位数字。
现在,浮点数的 repr()
使用与 str()
不同的格式精度。repr()
对于 C 的 sprintf()
使用 %.17g
格式字符串,而 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()
现在支持 ExtensionClass。这意味着您不再需要记住编写诸如 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.h
和 Include/objimpl.h
中的注释。对于在其中确定接口的漫长讨论,请参阅 python.org 上 ‘patches’ 和 ‘python-dev’ 列表的网络存档。
最新版本的 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()
。这些函数都接受一个模块对象、一个以 null 结尾的 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"])
C 扩展可能是最复杂的情况;这是一个从 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 还可以负责创建源代码和二进制发行版。“sdist”命令由“python setup.py sdist
”运行,构建一个源代码发行版,例如 foo-1.0.tar.gz
。添加新命令并不困难,“bdist_rpm”和“bdist_wininst”命令已经贡献出来,分别用于为软件创建 RPM 发行版和 Windows 安装程序。创建其他发行版格式(如 Debian 包和 Solaris .pkg
文件)的命令正处于不同的开发阶段。
所有这些都记录在一本新的手册《分发 Python 模块》中,该手册加入了 Python 文档的基本集合。
XML 模块¶
Python 1.5.2 以 xmllib
模块的形式包含了一个简单的 XML 解析器,该模块由 Sjoerd Mullender 贡献。自 1.5.2 发布以来,两种不同的 XML 处理接口变得很常见:SAX2(简单 XML API 的第 2 版)提供了一个事件驱动的接口,与 xmllib
有些相似之处,而 DOM(文档对象模型)提供了一个基于树的接口,将 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 文档,或 https://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 类,例如 Element
和 Text
,是 Node
基类的子类。因此,DOM 树中的所有节点都支持某些通用方法,例如 toxml()
,它返回一个包含节点及其子节点的 XML 表示形式的字符串。每个类也有自己的特殊方法;例如,Element
和 Document
实例有一个方法来查找所有具有给定标记名称的子元素。继续前面的两行示例
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 特殊兴趣小组已经从事 XML 相关的 Python 代码一段时间了。它的代码发行版名为 PyXML,可从 SIG 的网页 https://pythonlang.cn/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 的扩展标准库进行了许多改进和错误修复;一些受影响的模块包括 readline
、 ConfigParser
、 cgi
、 calendar
、 posix
、 readline
、 xmllib
、 aifc
、 chunk
、 wave
、 random
、 shelve
和 nntplib
。请查阅 CVS 日志以获取逐个补丁的详细信息。
Brian Gallew 为 socket
模块贡献了 OpenSSL 支持。OpenSSL 是安全套接字层的实现,它对通过套接字发送的数据进行加密。在编译 Python 时,您可以编辑 Modules/Setup
以包含 SSL 支持,这会在 socket
模块中添加一个额外的函数:socket.ssl(socket, keyfile, certfile)
,它接受一个套接字对象并返回一个 SSL 套接字。httplib
和 urllib
模块也进行了更改以支持 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_line
和 create_polygon
之类的操作更快,尤其是在使用大量坐标时。
curses
模块已大大扩展,从 Oliver Andrich 的增强版本开始,提供了来自 ncurses 和 SYSV curses 的许多附加功能,例如颜色、备用字符集支持、填充和鼠标支持。这意味着该模块不再与仅具有 BSD curses 的操作系统兼容,但似乎目前没有任何维护的操作系统属于此类别。
如前面关于 2.0 的 Unicode 支持的讨论中所述,re
模块提供的正则表达式的底层实现已更改。SRE 是 Fredrik Lundh 编写的新的正则表达式引擎,并由 Hewlett Packard 部分资助,它支持与 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
: 一个模块,与现有的ihooks
模块相比,它提供了一种更简单的方法来编写自定义导入钩子。(由 Greg Stein 实现,在 python-dev 上进行了大量讨论。)
IDLE 改进¶
IDLE 是官方的 Python 跨平台 IDE,使用 Tkinter 编写。Python 2.0 包括 IDLE 0.6,它添加了许多新功能和改进。以下是部分列表
UI 改进和优化,尤其是在语法高亮和自动缩进方面。
类浏览器现在显示更多信息,例如模块中的顶级函数。
制表符宽度现在是一个用户可设置的选项。当打开现有的 Python 文件时,IDLE 会自动检测缩进约定并进行调整。
现在支持在各种平台上调用浏览器,用于在浏览器中打开 Python 文档。
IDLE 现在有一个命令行,它与普通的 Python 解释器非常相似。
在许多地方添加了调用提示。
IDLE 现在可以作为软件包安装。
在编辑器窗口中,底部现在有一个行/列栏。
三个新的快捷键命令:检查模块(Alt-F5),导入模块(F5)和运行脚本(Ctrl-F5)。
删除和弃用的模块¶
一些模块已被删除,因为它们已过时,或者因为现在有更好的方法来做同样的事情。stdwin
模块已消失;它用于一个不再开发的平台无关的窗口工具包。
许多模块已移动到 lib-old
子目录:cmp
, cmpcache
, dircmp
, dump
, find
, grep
, packmail
, poly
, util
, whatsound
, zmod
。如果您的代码依赖于已移动到 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。