Python 2.2 的新特性 — Python 文档
Python 2.2 中的新功能
- 作者
- 是 库克林
简介
本文解释了 2002 年 10 月 14 日发布的 Python 2.2.2 中的新功能。 Python 2.2.2 是 Python 2.2 的错误修复版本,最初于 2001 年 12 月 21 日发布。
Python 2.2 可以被认为是“清理版本”。 有一些特性,如生成器和迭代器,是全新的,但大多数变化,尽管它们可能是重大和深远的,旨在清理语言设计的不规则和黑暗角落。
本文不尝试提供新功能的完整规范,而是提供一个方便的概述。 有关完整的详细信息,您应该参考 Python 2.2 的文档,例如 Python 库参考 和 Python 参考手册 。 如果您想了解更改的完整实现和设计原理,请参阅特定新功能的 PEP。
PEP 252 和 253:类型和类别变化
Python 2.2 中最大和影响最深远的变化是 Python 的对象和类模型。 更改应该是向后兼容的,因此您的代码可能会继续保持不变,但这些更改提供了一些惊人的新功能。 在开始这篇文章(本文最长和最复杂的部分)之前,我将概述更改并提供一些评论。
很久以前,我写了一个网页,列出了 Python 设计中的缺陷。 最重要的缺陷之一是不可能对用 C 实现的 Python 类型进行子类化。 特别是,不可能对内置类型进行子类化,因此您不能仅对列表进行子类化,以便向它们添加单个有用的方法。 UserList
模块提供了一个类,它支持列表的所有方法并且可以进一步子类化,但是有很多 C 代码需要常规的 Python 列表并且不接受 UserList
实例。
Python 2.2 修复了这个问题,并在此过程中添加了一些令人兴奋的新功能。 一个简短的总结:
- 您可以对内置类型(例如列表甚至整数)进行子类化,并且您的子类应该适用于需要原始类型的每个地方。
- 除了先前版本的 Python 中可用的实例方法之外,现在还可以定义静态和类方法。
- 还可以使用称为 properties 的新机制自动调用访问或设置实例属性的方法。
__getattr__()
的许多用途可以改写为使用属性,从而使生成的代码更简单、更快。 作为一个小方面的好处,属性现在也可以有文档字符串。 - 可以使用 slots 将实例的合法属性列表限制为特定集合,从而可以防止拼写错误,并可能在未来的 Python 版本中进行更多优化。
一些用户对所有这些变化表示担忧。 当然,他们说,新功能很简洁,可以使用在以前的 Python 版本中无法实现的各种技巧,但它们也使语言变得更加复杂。 有人说一直推荐Python,因为它的简单,觉得它的简单正在丢失。
个人觉得没必要太担心。 许多新功能非常深奥,您可以编写大量 Python 代码而无需了解它们。 编写一个简单的类并不比以往任何时候都困难,因此除非确实需要它们,否则您无需费心学习或教授它们。 一些以前只能从 C 中实现的非常复杂的任务现在可以在纯 Python 中实现,在我看来,这一切都变得更好了。
我不会试图涵盖使新功能发挥作用所需的每一个极端情况和小改动。 相反,本节将只绘制粗略的笔触。 有关 Python 2.2 新对象模型的更多信息来源,请参阅 相关链接 部分,“相关链接”。
新旧班级
首先,您应该知道 Python 2.2 确实有两种类:经典或旧式类,以及新式类。 旧式的类模型与早期版本的 Python 中的类模型完全相同。 本节中描述的所有新功能仅适用于新型类。 这种分歧不会永远持续下去。 最终旧式类将被删除,可能在 Python 3.0 中。
那么如何定义新式类呢? 您可以通过继承现有的新样式类来实现。 Python 的大多数内置类型,例如整数、列表、字典,甚至文件,现在都是新型类。 还添加了一个名为 object 的新样式类,它是所有内置类型的基类,因此如果没有合适的内置类型,您可以子类化 object:
class C(object):
def __init__ (self):
...
...
这意味着没有任何基类的 class 语句始终是 Python 2.2 中的经典类。 (实际上,您也可以通过设置名为 __metaclass__
的模块级变量来更改此设置 — 有关详细信息,请参阅 PEP 253 — 但将 子类化更容易对象。)
内置类型的类型对象可用作内置,使用巧妙的技巧命名。 Python 一直都有名为 int()、float() 和 str() 的内置函数。 在 2.2 中,它们不再是函数,而是在调用时表现为工厂的类型对象。
>>> int
<type 'int'>
>>> int('123')
123
为了使类型集完整,添加了新的类型对象,例如 dict() 和 file()
。 这是一个更有趣的例子,向文件对象添加一个 lock()
方法:
class LockableFile(file):
def lock (self, operation, length=0, start=0, whence=0):
import fcntl
return fcntl.lockf(self.fileno(), operation,
length, start, whence)
现在已经过时的 posixfile
模块包含一个模拟文件对象所有方法的类,还添加了一个 lock()
方法,但是这个类不能传递给期望内置的内部函数——在文件中,我们新的 LockableFile
可以做到这一点。
描述符
在以前的 Python 版本中,没有一致的方法来发现对象支持哪些属性和方法。 有一些非正式的约定,例如定义作为名称列表的 __members__
和 __methods__
属性,但扩展类型或类的作者通常不会费心定义它们。 您可以回过头来检查对象的 __dict__,但是当使用类继承或任意 __getattr__()
钩子时,这仍然可能不准确。
新类模型背后的一个重要思想是,使用 描述符 描述对象属性的 API 已被形式化。 描述符指定属性的值,说明它是方法还是字段。 使用描述符 API,静态方法和类方法以及更奇特的构造成为可能。
属性描述符是存在于类对象中的对象,并且有一些自己的属性:
- __name__ 是属性的名称。
__doc__
是属性的文档字符串。__get__(object)
是一种从 object 中检索属性值的方法。__set__(object, value)
将 object 上的属性设置为 value。__delete__(object, value)
删除对象的value属性。
比如写obj.x
时,Python实际执行的步骤是:
descriptor = obj.__class__.x
descriptor.__get__(obj)
对于方法,descriptor.__get__()
返回一个可调用的临时对象,并包装实例和要在其上调用的方法。 这也是为什么现在可以使用静态方法和类方法; 它们的描述符只包含方法,或者方法和类。 作为对这些新方法的简要说明,静态方法不传递实例,因此类似于常规函数。 类方法传递给对象的类,而不是对象本身。 静态和类方法定义如下:
class C(object):
def f(arg1, arg2):
...
f = staticmethod(f)
def g(cls, arg1, arg2):
...
g = classmethod(g)
staticmethod() 函数接受函数 f()
,并返回它包装在一个描述符中,以便它可以存储在类对象中。 您可能希望有用于创建此类方法的特殊语法(def static f
、defstatic f()
或类似的东西),但尚未定义此类语法; 这已留给 Python 的未来版本。
更多的新特性,比如槽和属性,也被实现为新的描述符类型,编写一个做一些新颖事情的描述符类并不难。 例如,可以编写一个描述符类,以便为方法编写 Eiffel 风格的前置条件和后置条件。 使用此功能的类可能定义如下:
from eiffel import eiffelmethod
class C(object):
def f(self, arg1, arg2):
# The actual function
...
def pre_f(self):
# Check preconditions
...
def post_f(self):
# Check postconditions
...
f = eiffelmethod(f, pre_f, post_f)
请注意,使用新的 eiffelmethod()
的人不必了解有关描述符的任何内容。 这就是为什么我认为新功能不会增加语言的基本复杂性。 为了编写 eiffelmethod()
或 ZODB 或其他任何东西,将有一些向导需要了解它,但大多数用户只会在生成的库之上编写代码而忽略实现细节。
多重继承:钻石法则
通过更改解析名称的规则,多重继承也变得更加有用。 考虑这组类(图表取自 Guido van Rossum 的 PEP 253):
class A:
^ ^ def save(self): ...
/ \
/ \
/ \
/ \
class B class C:
^ ^ def save(self): ...
\ /
\ /
\ /
\ /
class D
经典类的查找规则很简单,但不是很聪明; 基类是深度优先搜索的,从左到右。 对 D.save()
的引用将搜索类 D
、B
,然后是 A
,其中 save()
将被找到并返回。 C.save()
根本找不到。 这很糟糕,因为如果 C
的 save()
方法正在保存一些特定于 C
的内部状态,那么不调用它将导致该状态永远不会被保存。
新式类遵循不同的算法,解释起来有点复杂,但在这种情况下做正确的事情。 (请注意,Python 2.3 将此算法更改为在大多数情况下产生相同结果的算法,但对于真正复杂的继承图产生更有用的结果。)
- 列出所有基类,遵循经典查找规则,如果重复访问某个类,则多次包含该类。 在上面的例子中,访问类的列表是 [
D
,B
,A
,C
,A
]。 - 扫描列表中的重复类。 如果找到,则删除除一个之外的所有内容,将 last 保留在列表中。 在上面的示例中,列表在删除重复项后变为 [
D
,B
,C
,A
]。
按照这个规则,引用 D.save()
将返回 C.save()
,这是我们所追求的行为。 此查找规则与 Common Lisp 遵循的规则相同。 新的内置函数 super() 提供了一种无需重新实现 Python 算法即可获取类的超类的方法。 最常用的形式是 super(class, obj)
,它返回一个绑定的超类对象(不是实际的类对象)。 这种形式将在方法中用于调用超类中的方法; 例如,D
的 save()
方法如下所示:
class D (B,C):
def save (self):
# Call superclass .save()
super(D, self).save()
# Save D's private information here
...
super() 也可以在调用为 super(class)
或 super(class1, class2)
时返回未绑定的超类对象,但这可能不会经常有用。
属性访问
大量复杂的 Python 类使用 __getattr__()
定义了用于属性访问的钩子; 通常这样做是为了方便,通过自动将属性访问(例如 obj.parent
)映射到方法调用(例如 obj.get_parent
)来使代码更具可读性。 Python 2.2 添加了一些控制属性访问的新方法。
首先,__getattr__(attr_name)
仍然受到新样式类的支持,并且没有任何改变。 和以前一样,当尝试访问 obj.foo
并且在实例的字典中找不到名为 foo
的属性时,它将被调用。
新式类还支持一种新方法,__getattribute__(attr_name)
。 两种方法的区别在于,__getattribute__()
是 always 每当访问任何属性时调用,而旧的 __getattr__()
仅在 foo
不是时调用t 在实例的字典中找到。
然而,Python 2.2 对 属性 的支持通常是一种更简单的捕获属性引用的方法。 编写 __getattr__()
方法很复杂,因为为了避免递归,您不能在其中使用常规属性访问,而必须处理 __dict__ 的内容。 __getattr__()
方法在 Python 检查其他方法(例如 __repr__()
或 __coerce__()
)时也会被 Python 调用,因此在编写时必须牢记这一点。 最后,对每个属性访问调用一个函数会导致相当大的性能损失。
property 是一个新的内置类型,它封装了三个获取、设置或删除属性的函数和一个文档字符串。 例如,如果你想定义一个 size
计算的但也可以设置的属性,你可以写:
class C(object):
def get_size (self):
result = ... computation ...
return result
def set_size (self, size):
... compute something based on the size
and set internal state appropriately ...
# Define a property. The 'delete this attribute'
# method is defined as None, so the attribute
# can't be deleted.
size = property(get_size, set_size,
None,
"Storage size of this instance")
这当然比一对 __getattr__()
/__setattr__()
方法更清晰、更容易编写,这些方法检查 size
属性并在从实例的 [ X210X]__dict__。 对 size
的访问也是唯一必须执行调用函数的工作,因此对其他属性的引用以其通常的速度运行。
最后,可以使用新的 __slots__ 类属性来限制可以在对象上引用的属性列表。 Python 对象通常非常动态; 任何时候都可以通过执行 obj.new_attr=1
在实例上定义新属性。 新式类可以定义一个名为 __slots__ 的类属性,以将合法属性限制为一组特定的名称。 一个例子将说明这一点:
>>> class C(object):
... __slots__ = ('template', 'name')
...
>>> obj = C()
>>> print obj.template
None
>>> obj.template = 'Test'
>>> print obj.template
Test
>>> obj.newattr = None
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: 'C' object has no attribute 'newattr'
请注意在尝试分配给 __slots__ 中未列出的属性时如何获得 AttributeError。
PEP 234:迭代器
2.2 的另一个重要补充是 C 和 Python 级别的迭代接口。 对象可以定义调用者如何循环它们。
在 Python 2.1 之前的版本中,使 for item in obj
工作的通常方法是定义一个 __getitem__()
方法,如下所示:
def __getitem__(self, index):
return <next item>
__getitem__()
更适合用于定义对象的索引操作,以便您可以编写 obj[5]
来检索第六个元素。 当您仅使用它来支持 for 循环时,这有点误导。 考虑一些想要循环的类文件对象; index 参数基本上没有意义,因为该类可能假设将进行一系列 __getitem__()
调用,每次 index 递增一个。 换句话说,__getitem__()
方法的存在并不意味着使用 file[5]
随机访问第六个元素会起作用,尽管它确实应该起作用。
在 Python 2.2 中,迭代可以单独实现,并且 __getitem__()
方法可以限于真正支持随机访问的类。 迭代器的基本思想很简单。 新的内置函数 iter(obj)
或 iter(C, sentinel)
用于获取迭代器。 iter(obj)
返回对象 obj 的迭代器,而 iter(C, sentinel)
返回一个迭代器,它将调用可调用对象 C 直到它返回 sentinel 表示迭代器已完成。
Python 类可以定义一个 __iter__()
方法,该方法应该为对象创建并返回一个新的迭代器; 如果对象是它自己的迭代器,这个方法可以只返回 self
。 特别是,迭代器通常是它们自己的迭代器。 在 C 中实现的扩展类型可以实现一个 tp_iter 函数以返回一个迭代器,并且想要作为迭代器运行的扩展类型可以定义一个 tp_iternext 函数。
那么,毕竟这一切,迭代器实际上是做什么的? 它们有一个必需的方法,next(),它不接受任何参数并返回下一个值。 当没有更多值要返回时,调用 next() 应该引发 StopIteration 异常。
>>> L = [1,2,3]
>>> i = iter(L)
>>> print i
<iterator object at 0x8116870>
>>> i.next()
1
>>> i.next()
2
>>> i.next()
3
>>> i.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration
>>>
在 2.2 中,Python 的 for 语句不再需要序列; 它期望 iter() 将返回一个迭代器。 为了向后兼容和方便,为没有实现 __iter__()
或 tp_iter 槽的序列自动构造一个迭代器,所以 for i in [1,2,3]
仍然可以工作。 无论 Python 解释器在哪里循环遍历序列,它都会被更改为使用迭代器协议。 这意味着您可以执行以下操作:
>>> L = [1,2,3]
>>> i = iter(L)
>>> a,b,c = i
>>> a,b,c
(1, 2, 3)
迭代器支持已添加到一些 Python 的基本类型。 在字典上调用 iter() 将返回一个遍历其键的迭代器:
>>> m = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
... 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
>>> for key in m: print key, m[key]
...
Mar 3
Feb 2
Aug 8
Sep 9
May 5
Jun 6
Jul 7
Jan 1
Apr 4
Nov 11
Dec 12
Oct 10
这只是默认行为。 如果要迭代键、值或键/值对,可以显式调用 iterkeys()
、itervalues()
或 iteritems()
方法以获得适当的迭代器。 在一个小的相关更改中,in 运算符现在可用于字典,因此 key in dict
现在等效于 dict.has_key(key)
。
文件还提供了一个迭代器,它调用 readline() 方法,直到文件中没有更多行。 这意味着您现在可以使用如下代码读取文件的每一行:
for line in file:
# do something for each line
...
请注意,您只能在迭代器中前进; 无法获取前一个元素、重置迭代器或复制它。 迭代器对象可以提供这样的附加功能,但迭代器协议只需要一个 next() 方法。
PEP 255:简单的生成器
生成器是另一个新特性,它与迭代器的引入交互。
您无疑熟悉函数调用在 Python 或 C 中的工作方式。 当您调用一个函数时,它会获得一个私有命名空间,在该命名空间中创建了它的局部变量。 当函数到达 return 语句时,局部变量被销毁,结果值返回给调用者。 稍后对同一函数的调用将获得一组全新的局部变量。 但是,如果在退出函数时没有丢弃局部变量呢? 如果您稍后可以恢复它停止的功能怎么办? 这就是生成器提供的; 它们可以被认为是可恢复的功能。
这是生成器函数的最简单示例:
def generate_ints(N):
for i in range(N):
yield i
为生成器引入了一个新关键字 yield。 任何包含 yield
语句的函数都是生成器函数; 这是由 Python 的字节码编译器检测到的,该编译器因此专门编译该函数。 由于引入了新关键字,因此必须通过在模块源代码顶部附近包含 from __future__ import generators
语句在模块中显式启用生成器。 在 Python 2.3 中,此语句将变得不必要。
当您调用生成器函数时,它不会返回单个值; 相反,它返回一个支持迭代器协议的生成器对象。 在执行 yield 语句时,生成器输出 i
的值,类似于 return 语句。 yield
和 return
语句之间的最大区别在于,在到达 yield
时,生成器的执行状态被挂起并保留局部变量。 在下次调用生成器的 next()
方法时,该函数将在 yield
语句之后立即恢复执行。 (出于复杂的原因,try…finally 语句的 try
块中不允许使用 yield
语句;阅读 [ X151X]PEP 255 的完整解释 yield
和异常之间的相互作用。)
以下是 generate_ints()
生成器的示例用法:
>>> gen = generate_ints(3)
>>> gen
<generator object at 0x8117f90>
>>> gen.next()
0
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 2, in generate_ints
StopIteration
你同样可以写 for i in generate_ints(5)
或 a,b,c = generate_ints(3)
。
在生成器函数内部,return 语句只能在没有值的情况下使用,并表示值的处理结束; 之后生成器无法返回任何其他值。 return
带有一个值,例如 return 5
,是生成器函数内部的语法错误。 生成器结果的结束也可以通过手动提高 StopIteration 来指示,或者只是让执行流程从函数的底部落下。
您可以通过编写自己的类并将生成器的所有局部变量存储为实例变量来手动实现生成器的效果。 例如,可以通过将 self.count
设置为 0 并让 next() 方法递增 self.count
并返回它来返回整数列表。 但是,对于中等复杂度的生成器,编写相应的类会麻烦得多。 Lib/test/test_generators.py
包含许多更有趣的例子。 最简单的方法是使用生成器递归地实现树的有序遍历。
# A recursive generator that generates Tree leaves in in-order.
def inorder(t):
if t:
for x in inorder(t.left):
yield x
yield t.label
for x in inorder(t.right):
yield x
Lib/test/test_generators.py
中的另外两个例子为 N-Queens 问题(将 $N$ 个皇后放在 $NxN$ 棋盘上,这样没有皇后威胁另一个)和骑士之旅(一条带骑士到$NxN$ 棋盘的每个方格,而无需两次访问任何方格)。
生成器的思想来自其他编程语言,尤其是 Icon (https://www.cs.arizona.edu/icon/),其中生成器的思想是核心。 在 Icon 中,每个表达式和函数调用都像一个生成器。 https://www.cs.arizona.edu/icon/docs/ipd266.htm 中的“图标编程语言概述”中的一个示例给出了它的外观:
sentence := "Store it in the neighboring harbor"
if (i := find("or", sentence)) > 5 then write(i)
在 Icon 中,find()
函数返回找到子字符串“或”的索引:3、23、33。 在if语句中,i
首先被赋值为3,但是3小于5,所以比较失败,Icon用第二个值23重试。 23 大于 5,所以现在比较成功,代码将值 23 打印到屏幕上。
在采用生成器作为核心概念方面,Python 远不如 Icon。 生成器被认为是核心 Python 语言的一个新部分,但学习或使用它们不是强制性的; 如果它们不能解决您遇到的任何问题,请随时忽略它们。 与 Icon 的接口相比,Python 接口的一个新特性是生成器的状态表示为一个具体的对象(迭代器),它可以传递给其他函数或存储在数据结构中。
也可以看看
- PEP 255 - 简单的生成器
- 由 Neil Schemenauer、Tim Peters、Magnus Lie Hetland 撰写。 主要由 Neil Schemenauer 和 Tim Peters 实施,其他修复来自 Python Labs 团队。
PEP 237:统一长整数和整数
在最近的版本中,常规整数(在大多数机器上是 32 位值)和长整数(可以是任意大小)之间的区别变得令人烦恼。 例如,在支持大于 2**32
字节的文件的平台上,文件对象的 tell()
方法必须返回一个长整数。 但是,Python 的许多位都需要纯整数,如果提供长整数,则会引发错误。 例如,在 Python 1.5 中,只能将常规整数用作切片索引,而 'abc'[1L:]
会引发 TypeError 异常,并显示消息“切片索引必须为整数”。
Python 2.2 将根据需要将值从短整数转换为长整数。 不再需要“L”后缀来表示长整型文字,因为现在编译器会选择合适的类型。 (在 Python 的未来 2.x 版本中将不鼓励使用“L”后缀,在 Python 2.4 中触发警告,并可能在 Python 3.0 中删除。)许多曾经引发 OverflowError 的操作现在将返回一个长整数作为结果。 例如:
>>> 1234567890123
1234567890123L
>>> 2 ** 64
18446744073709551616L
在大多数情况下,整数和长整数现在将被同等对待。 您仍然可以使用 type() 内置函数区分它们,但这很少需要。
PEP 238:更改除法运算符
Python 2.2 中最具争议的变化预示着修复 Python 从一开始就存在的旧设计缺陷的努力的开始。 当前,Python 的除法运算符 /
在带有两个整数参数时的行为类似于 C 的除法运算符:它返回一个整数结果,当有小数部分时会被截断。 例如,3/2
是 1,而不是 1.5,而 (-1)/2
是 -1,而不是 -0.5。 这意味着除法的结果可能会根据两个操作数的类型发生意外变化,并且由于 Python 是动态类型的,因此很难确定操作数的可能类型。
(争议在于这是否 真的 是一个设计缺陷,以及是否值得破坏现有代码来解决这个问题。 它在 python-dev 上引起了无休止的讨论,并在 2001 年 7 月在 comp.lang.python 上爆发了一阵激烈的讽刺帖子。 我不会在这里争论任何一方,而是会坚持描述 2.2 中实现的内容。 阅读 PEP 238 以获取论点和反论点的摘要。)
由于此更改可能会破坏代码,因此正在逐步引入。 Python 2.2 开始转换,但转换要到 Python 3.0 才能完成。
首先,我将从 PEP 238 借用一些术语。 “真正的除法”是大多数非程序员都熟悉的除法:3/2 是 1.5,1/4 是 0.25,依此类推。 “地板除法”是 Python 的 /
运算符当前在给定整数操作数时所做的事情; 结果是真正除法返回的值的下限。 “经典分割”是目前/
的混搭行为; 当操作数是整数时,它返回地板除法的结果,当其中一个操作数是浮点数时,它返回真除法的结果。
以下是 2.2 引入的更改:
新的运算符
//
是楼层划分运算符。 (是的,我们知道它看起来像 C++ 的注释符号。)//
无论操作数是什么类型, 总是执行楼层划分,所以1 // 2
是 0 和 [ X170X] 也是 0.0。//
在 Python 2.2 中始终可用; 您不需要使用__future__
语句启用它。通过在模块中包含
from __future__ import division
,/
运算符将被更改为返回真正除法的结果,因此1/2
为 0.5。 如果没有__future__
语句,/
仍然表示经典除法。/
的默认含义直到 Python 3.0 才会改变。类可以定义称为
__truediv__()
和__floordiv__()
的方法来重载两个除法运算符。 在 C 级别,PyNumberMethods 结构中也有插槽,因此扩展类型可以定义两个运算符。Python 2.2 支持一些命令行参数来测试代码是否可以使用更改后的除法语义。 使用
-Q warn
运行 python 将导致在对两个整数应用除法时发出警告。 您可以使用它来查找受更改影响的代码并修复它。 默认情况下,Python 2.2 将简单地执行经典除法而不会发出警告; 在 Python 2.3 中,警告将默认开启。
Unicode 更改
Python 的 Unicode 支持在 2.2 中有所增强。 Unicode 字符串通常存储为 UCS-2,即 16 位无符号整数。 通过向配置脚本提供 --enable-unicode=ucs4
,还可以编译 Python 2.2 以使用 UCS-4、32 位无符号整数作为其内部编码。 (也可以指定 --disable-unicode
以完全禁用 Unicode 支持。)
当构建为使用 UCS-4(“宽 Python”)时,解释器可以本地处理从 U+000000 到 U+110000 的 Unicode 字符,因此 unichr()
函数的合法值范围相应扩大。 使用编译为使用 UCS-2(“窄 Python”)的解释器,大于 65535 的值仍会导致 unichr()
引发 ValueError 异常。 这在 PEP 261,“支持‘宽’Unicode 字符”中都有描述; 详情请咨询。
另一个变化更容易解释。 自引入以来,Unicode 字符串支持 encode()
方法将字符串转换为选定的编码,例如 UTF-8 或 Latin-1。 在 2.2 中,对称 decode([*encoding*])
方法已添加到 8 位字符串(尽管不是 Unicode 字符串)。 decode()
假定字符串采用指定的编码并对其进行解码,返回编解码器返回的任何内容。
使用此新功能,为与 Unicode 不直接相关的任务添加了编解码器。 例如,已添加用于 uu 编码、MIME 的 base64 编码和使用 zlib 模块进行压缩的编解码器:
>>> s = """Here is a lengthy piece of redundant, overly verbose,
... and repetitive text.
... """
>>> data = s.encode('zlib')
>>> data
'x\x9c\r\xc9\xc1\r\x80 \x10\x04\xc0?Ul...'
>>> data.decode('zlib')
'Here is a lengthy piece of redundant, overly verbose,\nand repetitive text.\n'
>>> print s.encode('uu')
begin 666 <data>
M2&5R92!I<R!A(&QE;F=T:'D@<&EE8V4@;V8@<F5D=6YD86YT+"!O=F5R;'D@
>=F5R8F]S92P*86YD(')E<&5T:71I=F4@=&5X="X*
end
>>> "sheesh".encode('rot-13')
'furrfu'
要将类实例转换为 Unicode,可以通过类定义 __unicode__()
方法,类似于 __str__()
。
encode()
、decode()
和 __unicode__()
由 Marc-André Lemburg 实现。 在内部支持使用 UCS-4 的更改由 Fredrik Lundh 和 Martin von Löwis 实施。
PEP 227:嵌套作用域
在 Python 2.1 中,静态嵌套作用域被添加为可选功能,由 from __future__ import nested_scopes
指令启用。 在 2.2 中,嵌套作用域不再需要特别启用,现在始终存在。 本节的其余部分是我的“Python 2.1 中的新功能”文档中嵌套作用域描述的副本; 如果您在 2.1 发布时阅读它,则可以跳过本节的其余部分。
Python 2.1 中引入并在 2.2 中完成的最大变化是 Python 的作用域规则。 在 Python 2.0 中,在任何给定时间最多有三个命名空间用于查找变量名称:本地、模块级和内置命名空间。 这常常让人们感到惊讶,因为它不符合他们的直觉预期。 例如,嵌套递归函数定义不起作用:
def f():
...
def g(value):
...
return g(value-1) + 1
...
函数 g()
将始终引发 NameError 异常,因为名称 g
的绑定不在其本地命名空间或模块级命名空间中。 这在实践中不是什么大问题(你多久像这样递归地定义内部函数?),但这也使得使用 lambda 表达式变得笨拙,这在实践中是一个问题。 在使用 lambda
的代码中,您通常可以通过将局部变量作为参数的默认值传递来发现被复制的局部变量。
def find(self, name):
"Return list of any entries equal to 'name'"
L = filter(lambda x, name=name: x == name,
self.list_attribute)
return L
因此,以强函数风格编写的 Python 代码的可读性受到很大影响。
Python 2.2 最重要的变化是在语言中添加了静态作用域来解决这个问题。 作为第一个效果,在上面的例子中 name=name
默认参数现在是不必要的。 简而言之,当给定的变量名未在函数内赋值时(通过赋值,或 def、class 或 import 语句),对变量的引用将在封闭作用域的本地命名空间中查找。 可以在 PEP 中找到对规则的更详细解释以及对实现的剖析。
对于在模块级别使用相同变量名称和作为包含更多函数定义的函数中的局部变量的代码,此更改可能会导致一些兼容性问题。 不过,这似乎不太可能,因为这样的代码一开始读起来会很混乱。
更改的一个副作用是 from module import *
和 exec
语句在某些条件下在函数作用域内是非法的。 Python 参考手册一直说 from module import *
只在模块的顶层是合法的,但 CPython 解释器之前从未强制执行过。 作为嵌套作用域实现的一部分,将 Python 源代码转换为字节码的编译器必须生成不同的代码来访问包含作用域中的变量。 from module import *
和 exec
使编译器无法弄清楚这一点,因为它们向本地命名空间添加了在编译时不可知的名称。 因此,如果函数包含函数定义或带有自由变量的 lambda 表达式,编译器将通过引发 SyntaxError 异常来标记它。
为了让前面的解释更清楚一些,这里有一个例子:
x = 1
def f():
# The next line is a syntax error
exec 'x=2'
def g():
return x
包含 exec
语句的第 4 行是一个语法错误,因为 exec
将定义一个名为 x
的新局部变量,其值应由 g()
访问。
这不应该是一个很大的限制,因为 exec
在大多数 Python 代码中很少使用(并且当它被使用时,它通常是一个糟糕设计的标志)。
新的和改进的模块
xmlrpclib
模块由 Fredrik Lundh 贡献给标准库,为编写 XML-RPC 客户端提供支持。 XML-RPC 是一种建立在 HTTP 和 XML 之上的简单远程过程调用协议。 例如,以下代码段从 O'Reilly Network 检索 RSS 频道列表,然后列出一个频道的最近头条新闻:import xmlrpclib s = xmlrpclib.Server( 'http://www.oreillynet.com/meerkat/xml-rpc/server.php') channels = s.meerkat.getChannels() # channels is a list of dictionaries, like this: # [{'id': 4, 'title': 'Freshmeat Daily News'} # {'id': 190, 'title': '32Bits Online'}, # {'id': 4549, 'title': '3DGamers'}, ... ] # Get the items for one channel items = s.meerkat.getItems( {'channel': 4} ) # 'items' is another list of dictionaries, like this: # [{'link': 'http://freshmeat.net/releases/52719/', # 'description': 'A utility which converts HTML to XSL FO.', # 'title': 'html2fo 0.3 (Default)'}, ... ]
SimpleXMLRPCServer
模块使创建简单的 XML-RPC 服务器变得容易。 有关 XML-RPC 的更多信息,请参阅 http://xmlrpc.scripting.com/。最初返回冗长元组的几个函数现在返回伪序列,这些伪序列的行为仍然像元组,但也具有诸如memberst_mtime 或
tm_year
之类的助记符属性。 增强功能包括os模块中的stat()、fstat()
、statvfs()
、fstatvfs()
和localtime()
、gmtime()
和strptime()
。例如,要使用旧元组获取文件大小,您最终会写出类似
file_size = os.stat(filename)[stat.ST_SIZE]
的内容,但现在可以更清楚地写成file_size = os.stat(filename).st_size
。此功能的原始补丁由 Nick Mathewson 提供。
Python 探查器已被广泛修改,其输出中的各种错误已得到纠正。 (供稿人弗雷德 L。 德雷克,小 和蒂姆·彼得斯。)
socket模块可以编译支持IPv6; 为 Python 的配置脚本指定
--enable-ipv6
选项。 (由 Jun-ichiro “itojun” Hagino 提供。)在支持 C long long 类型的平台上的 64 位整数的 struct 模块中添加了两个新的格式字符。
q
表示有符号的 64 位整数,Q
表示无符号整数。 该值以 Python 的长整数类型返回。 (由蒂姆·彼得斯提供。)在解释器的交互模式下,有一个新的内置函数 help(),它使用 Python 2.1 中引入的 pydoc 模块来提供交互式帮助。
help(object)
显示关于 object 的任何可用帮助文本。 help() 不带参数将您置于在线帮助实用程序中,您可以在其中输入函数、类或模块的名称以阅读其帮助文本。 (由 Guido van Rossum 提供,使用 Ka-Ping Yee 的 pydoc 模块。)对 re 模块下的 SRE 引擎进行了各种错误修复和性能改进。 例如,re.sub() 和 re.split() 函数已用 C 重写。 另一个贡献的补丁将某些 Unicode 字符范围加速了两倍,以及一个新的
finditer()
方法,该方法返回给定字符串中所有非重叠匹配的迭代器。 (SRE 由 Fredrik Lundh 维护。 BIGCHARSET 补丁由 Martin von Löwis 贡献。)smtplib 模块现在支持 RFC 2487,“基于 TLS 的安全 SMTP”,因此现在可以加密 Python 程序和邮件传输之间的 SMTP 流量代理收到一条消息。 smtplib 也支持 SMTP 认证。 (由 Gerhard Häring 提供。)
由 Piers Lauder 维护的 imaplib 模块支持多个新扩展:RFC 2342、SORT、GETACL 和 SETACL 中定义的 NAMESPACE 扩展。 (安东尼·巴克斯特和米歇尔·佩尔蒂埃供稿。)
rfc822
模块对电子邮件地址的解析现在符合 RFC 2822,这是对 RFC 822 的更新。 (该模块的名称是 而不是 将更改为rfc2822
。)还添加了一个新包 email,用于解析和生成电子邮件消息。 (由 Barry Warsaw 贡献,源于他对 Mailman 的工作。)difflib 模块现在包含一个新的
Differ
类,用于在两个文本行序列之间生成人类可读的更改列表(“增量”)。 还有两个生成器函数ndiff()
和restore()
,分别从两个序列返回一个delta,或者从一个delta返回原始序列之一。 (David Goodger 贡献的 Grunt 工作,来自 Tim Peters 的 ndiff.py 代码,然后他做了生成器。)新常量
ascii_letters
、ascii_lowercase
和ascii_uppercase
被添加到 string 模块。 标准库中有几个模块使用string.letters
来表示范围 A-Za-z,但是当使用语言环境时,这种假设是不正确的,因为string.letters
因当前语言环境定义的合法字符。 有问题的模块都已修复为使用ascii_letters
代替。 (由不知名人士报道;由 Fred L. 小德雷克)mimetypes 模块现在通过添加
MimeTypes
类使使用替代 MIME 类型数据库变得更容易,该类需要解析文件名列表。 (供稿人弗雷德 L。 小德雷克)Timer
类被添加到 threading 模块中,允许在未来某个时间安排活动。 (由 Itamar Shtull-Trauring 提供。)
解释器更改和修复
一些更改仅影响在 C 级别处理 Python 解释器的人,因为他们正在编写 Python 扩展模块、嵌入解释器或只是对解释器本身进行黑客攻击。 如果您只编写 Python 代码,那么这里描述的任何更改都不会对您产生太大影响。
分析和跟踪函数现在可以用 C 实现,它的运行速度比基于 Python 的函数快得多,并且应该减少分析和跟踪的开销。 Python 开发环境的作者会对此感兴趣。 Python 的 API 中添加了两个新的 C 函数,PyEval_SetProfile() 和 PyEval_SetTrace()。 现有的 sys.setprofile() 和 sys.settrace() 函数仍然存在,只是简单地更改为使用新的 C 级接口。 (供稿人弗雷德 L。 小德雷克)
添加了另一个低级 API,主要是 Python 调试器和开发工具的实现者感兴趣的。 PyInterpreterState_Head() 和 PyInterpreterState_Next() 让调用者遍历所有现有的解释器对象; PyInterpreterState_ThreadHead() 和 PyThreadState_Next() 允许循环遍历给定解释器的所有线程状态。 (大卫·比兹利供稿。)
垃圾收集器的 C 级接口已更改,以便更轻松地编写支持垃圾收集的扩展类型并调试函数的误用。 各种函数的语义略有不同,因此必须对一堆函数进行重命名。 使用旧 API 的扩展仍将编译,但 不会 参与垃圾收集,因此应将它们更新为 2.2 的优先级较高。
要将扩展模块升级到新 API,请执行以下步骤:
将
Py_TPFLAGS_GC()
重命名为PyTPFLAGS_HAVE_GC()
。- 使用 PyObject_GC_New() 或 PyObject_GC_NewVar() 分配
对象,并 PyObject_GC_Del() 解除分配它们。
- 将
PyObject_GC_Init()
重命名为 PyObject_GC_Track() 和 PyObject_GC_Fini()
到 PyObject_GC_UnTrack()。
- 将
从对象大小计算中删除
PyGC_HEAD_SIZE()
。删除对
PyObject_AS_GC()
和PyObject_FROM_GC()
的调用。PyArg_ParseTuple()添加了新的
et
格式序列;et
接受一个参数和一个编码名称,如果参数是 Unicode 字符串,则将参数转换为给定的编码,如果是 8 位字符串,则将其转换为给定的编码,假设它已经处于所需的编码中。 这与es
格式字符不同,后者假定 8 位字符串采用 Python 的默认 ASCII 编码,并将它们转换为指定的新编码。 (由 M.-A. Lemburg,用于下一节中描述的 Windows 上的 MBCS 支持。)添加了一个不同的参数解析函数 PyArg_UnpackTuple(),它更简单,可能更快。 调用者没有指定格式字符串,而是简单地给出预期的最小和最大参数数,以及一组指向 PyObject* 变量的指针,这些变量将用参数值填充。
方法定义表中提供了两个新标志 METH_NOARGS 和 METH_O,以简化没有参数或单个无类型参数的方法的实现。 调用此类方法比调用使用 METH_VARARGS 的相应方法更有效。 此外,旧的
METH_OLDARGS
编写 C 方法的风格现已正式弃用。添加了两个新的包装器函数 PyOS_snprintf() 和 PyOS_vsnprintf(),以为相对较新的
snprintf()
和vsnprintf()
C 提供跨平台实现库 API。 与标准的sprintf()
和vsprintf()
函数相比,Python 版本检查用于防止缓冲区溢出的缓冲区边界。 (由 M.-A. 伦堡。)_PyTuple_Resize() 函数丢失了一个未使用的参数,因此现在需要 2 个参数而不是 3 个。 第三个参数从未使用过,在将代码从早期版本移植到 Python 2.2 时可以简单地丢弃。
其他更改和修复
像往常一样,在整个源代码树中散布着许多其他改进和错误修复。 通过 CVS 更改日志搜索发现,在 Python 2.1 和 2.2 之间应用了 527 个补丁并修复了 683 个错误; 2.2.1 应用139个补丁,修复143个bug; 2.2.2 应用了 106 个补丁并修复了 82 个错误。 这些数字很可能被低估了。
一些更显着的变化是:
由 Jack Jansen 维护的 Python 的 MacOS 端口代码现在保存在主要的 Python CVS 树中,并且已经进行了许多更改以支持 MacOS X。
最重要的变化是能够将 Python 构建为框架,通过在编译 Python 时向配置脚本提供
--enable-framework
选项来启用。 根据 Jack Jansen 的说法,“这会将独立的 Python 安装和 OS X 框架“粘合”到/Library/Frameworks/Python.framework
(或其他选择的位置)中。 目前,这几乎没有立即增加的好处(实际上,缺点是您必须更改 PATH 才能找到 Python),但它是创建成熟的 Python 应用程序、移植 MacPython IDE 的基础,可能使用 Python 作为标准 OSA 脚本语言等等。”大多数 MacPython 工具箱模块,它们与 MacOS API 接口,如窗口、QuickTime、脚本等。 已移植到 OS X,但它们已在
setup.py
中被注释掉。 想要试验这些模块的人可以手动取消对它们的注释。传递给不接受它们的内置函数的关键字参数现在会导致引发 TypeError 异常,并显示消息“function 不接受关键字参数”。
在 Python 2.1 中作为扩展模块添加的弱引用现在是核心的一部分,因为它们用于新式类的实现。 ReferenceError 异常因此从 weakref 模块移到了内置异常。
Tim Peters 的新脚本
Tools/scripts/cleanfuture.py
自动从 Python 源代码中删除过时的__future__
语句。内置函数 compile() 中添加了一个额外的 flags 参数,因此现在可以在模拟 shell 中正确观察
__future__
语句的行为,例如就像 IDLE 和其他开发环境所呈现的那样。 这在 PEP 264 中有描述。 (迈克尔·哈德森供稿。)Python 1.6 引入的新许可证与 GPL 不兼容。 这是通过对 2.2 许可证进行一些小的文本更改来修复的,因此现在再次将 Python 嵌入 GPL 程序中是合法的。 请注意,Python 本身不是 GPL,而是遵循本质上等同于 BSD 许可证的许可证,一如既往。 许可证更改也适用于 Python 2.0.1 和 2.1.1 版本。
当在 Windows 上显示 Unicode 文件名时,Python 现在会将其转换为 Microsoft 文件 API 使用的 MBCS 编码字符串。 由于文件 API 显式使用 MBCS,因此 Python 选择 ASCII 作为默认编码是一件麻烦事。 在 Unix 上,如果
locale.nl_langinfo(CODESET)
可用,则使用语言环境的字符集。 (Windows 支持由 Mark Hammond 在 Marc-André Lemburg 的协助下提供。 Unix 支持由 Martin von Löwis 添加。)现在在 Windows 上启用了大文件支持。 (由蒂姆·彼得斯提供。)
Tools/scripts/ftpmirror.py
脚本现在解析.netrc
文件(如果有的话)。 (迈克·罗姆伯格供稿。)xrange()
函数返回的对象的某些功能现已弃用,并在访问时触发警告; 它们将在 Python 2.3 中消失。xrange
对象试图通过支持切片、序列乘法和 in 运算符来假装它们是完整的序列类型,但这些功能很少使用,因此存在缺陷。tolist()
方法和start
、stop
和step
属性也被弃用。 在 C 级别,PyRange_New()
函数的第四个参数repeat
也已弃用。字典实现有很多补丁,主要是为了修复潜在的核心转储,如果字典包含偷偷改变其哈希值的对象,或者改变了它们所在的字典。 有一段时间,python-dev 陷入了 Michael Hudson 找到一个倾倒核心的案例、Tim Peters 修复错误、Michael 找到另一个案例的温和节奏,然后又一圈又一圈地进行。
在 Windows 上,Python 现在可以使用 Borland C 编译,这要归功于 Stephen Hansen 提供的许多补丁,尽管结果还没有完全发挥作用。 (但是这个是进步……)
另一个 Windows 增强功能:Wise Solutions 慷慨地提供 PythonLabs 使用他们的 InstallerMaster 8.1 系统。 早期的 PythonLabs Windows 安装程序使用 Wise 5.0a,它开始显示其年龄。 (由蒂姆彼得斯打包。)
现在可以在 Windows 上导入以
.pyw
结尾的文件。.pyw
是 Windows 独有的东西,用于表示需要使用 PYTHONW.EXE 而不是 PYTHON.EXE 来运行脚本,以防止弹出 DOS 控制台以显示输出。 此补丁使导入此类脚本成为可能,以防它们也可用作模块。 (由大卫博伦实施。)在 Python 使用 C
dlopen()
函数加载扩展模块的平台上,现在可以使用 sys.getdlopenflags() 和 设置dlopen()
使用的标志]sys.setdlopenflags() 函数。 (由 Bram Stolk 提供。)pow() 内置函数在提供浮点数时不再支持 3 个参数。
pow(x, y, z)
返回(x**y) % z
,但这对浮点数从来没有用,最终结果因平台而异。 诸如pow(2.0, 8.0, 7.0)
之类的调用现在将引发 TypeError 异常。
致谢
作者要感谢以下人员对本文的各种草稿提供建议、更正和帮助:Fred Bremmer、Keith Briggs、Andrew Dalke、Fred L. Drake, Jr., Carel Fellinger, David Goodger, Mark Hammond, Stephen Hansen, Michael Hudson, Jack Jansen, Marc-André Lemburg, Martin von Löwis, Fredrik Lundh, Michael McLay, Nick Mathewson, Paul Moore, Gustavo Niemeyer, Don O'唐内尔、乔纳斯·帕拉斯玛、蒂姆·彼得斯、延斯·奎德、汤姆·莱因哈特、尼尔·斯普诺尔、吉多·范罗苏姆、格雷格·沃德、爱德华·韦尔伯恩。