Python 2.4 新特性¶
- 作者:
A.M. Kuchling
本文解释了 Python 2.4.1(2005 年 3 月 30 日发布)中的新特性。
Python 2.4 是一个中等规模的版本。它不像激进的 Python 2.2 那样引入了大量变化,但比保守的 2.3 版本引入了更多功能。最重要的新语言特性是函数装饰器和生成器表达式;其他大部分变化都在标准库中。
根据 CVS 更改日志,Python 2.3 和 2.4 之间应用了 481 个补丁,修复了 502 个错误。这两个数字可能都被低估了。
本文不试图提供每个新功能的完整规范,而是对每个功能进行简要介绍。要了解完整详细信息,请参阅 Python 2.4 的文档,例如 Python 库参考和 Python 参考手册。通常,您会被引导到特定新功能的 PEP,以了解其实现和设计原理。
PEP 218:内置集合对象¶
Python 2.3 引入了 sets
模块。集合数据类型的 C 实现现已作为两个新的内置类型 set(iterable)
和 frozenset(iterable)
添加到 Python 核心中。它们为成员测试、从序列中消除重复项以及并集、交集、差集和对称差集等数学运算提供了高速操作。
>>> a = set('abracadabra') # form a set from a string
>>> 'z' in a # fast membership testing
False
>>> a # unique letters in a
set(['a', 'r', 'b', 'c', 'd'])
>>> ''.join(a) # convert back into a string
'arbcd'
>>> b = set('alacazam') # form a second set
>>> a - b # letters in a but not in b
set(['r', 'd', 'b'])
>>> a | b # letters in either a or b
set(['a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'])
>>> a & b # letters in both a and b
set(['a', 'c'])
>>> a ^ b # letters in a or b but not both
set(['r', 'd', 'b', 'm', 'z', 'l'])
>>> a.add('z') # add a new element
>>> a.update('wxy') # add multiple new elements
>>> a
set(['a', 'c', 'b', 'd', 'r', 'w', 'y', 'x', 'z'])
>>> a.remove('x') # take one element out
>>> a
set(['a', 'c', 'b', 'd', 'r', 'w', 'y', 'z'])
frozenset()
类型是 set()
的不可变版本。由于它是不可变且可哈希的,因此可以用作字典键或另一个集合的成员。
sets
模块保留在标准库中,如果您希望对 Set
或 ImmutableSet
类进行子类化,它可能会很有用。目前没有计划弃用该模块。
参见
- PEP 218 - 添加内置集合对象类型
最初由 Greg Wilson 提出,最终由 Raymond Hettinger 实现。
PEP 237:统一长整数和整数¶
这项 PEP 的漫长过渡过程始于 Python 2.2,在 Python 2.4 中又向前迈进了一步。在 2.3 中,某些在 int/long 统一后行为会有所不同的整数操作会触发 FutureWarning
警告并返回限制为 32 或 64 位(取决于您的平台)的值。在 2.4 中,这些表达式不再产生警告,而是产生一个不同的结果,通常是一个长整数。
有问题的表达式主要是左移和冗长的十六进制和八进制常量。例如,2 << 32
在 2.3 中会导致警告,在 32 位平台上评估为 0。在 Python 2.4 中,此表达式现在返回正确答案 8589934592。
参见
- PEP 237 - 统一长整数和整数
原始 PEP 由 Moshe Zadka 和 GvR 撰写。2.4 的更改由 Kalle Svensson 实现。
PEP 289:生成器表达式¶
Python 2.2 中引入的迭代器功能和 itertools
模块使得编写程序更容易遍历大型数据集,而无需一次性将整个数据集加载到内存中。列表推导式在这种情况下表现不佳,因为它们会生成一个包含所有项的 Python 列表对象。这不可避免地会将所有对象加载到内存中,如果数据集非常大,这可能会成为问题。在尝试编写函数式风格的程序时,自然会写出类似以下的代码:
links = [link for link in get_all_links() if not link.followed]
for link in links:
...
而不是
for link in get_all_links():
if link.followed:
continue
...
第一种形式更简洁,可能更具可读性,但如果您处理大量链接对象,则必须编写第二种形式以避免所有链接对象同时存在于内存中。
生成器表达式的工作方式类似于列表推导式,但不会具体化整个列表;相反,它们会创建一个生成器,一个接一个地返回元素。上面的例子可以写成
links = (link for link in get_all_links() if not link.followed)
for link in links:
...
生成器表达式总是必须写在括号内,如上面的例子所示。表示函数调用的括号也算数,所以如果你想创建一个将立即传递给函数的迭代器,你可以写
print sum(obj.count for obj in list_all_objects())
生成器表达式与列表推导式在各种细微之处有所不同。最值得注意的是,循环变量(在上面的例子中是 obj)在生成器表达式之外无法访问。列表推导式将变量分配给其最后一个值;Python 的未来版本将改变这一点,使列表推导式在这方面与生成器表达式匹配。
参见
- PEP 289 - 生成器表达式
由 Raymond Hettinger 提出,Jiwon Seo 实现,Hye-Shik Chang 早期指导。
PEP 292:更简单的字符串替换¶
标准库中的一些新类提供了另一种将变量替换到字符串中的机制;这种替换方式可能更适合需要非专业用户编辑模板的应用程序。
按名称替换变量的常用方法是 %
运算符
>>> '%(page)i: %(title)s' % {'page':2, 'title': 'The Best of Times'}
'2: The Best of Times'
在编写模板字符串时,很容易忘记右括号后的 i
或 s
。如果模板在 Python 模块中,这并不是一个大问题,因为您运行代码,得到一个“不支持的格式字符”ValueError
,然后修复问题。但是,考虑像 Mailman 这样的应用程序,其中模板字符串或翻译由不了解 Python 语言的用户编辑。格式字符串的语法对于这些用户来说解释起来很复杂,如果他们犯了错误,很难向他们提供有用的反馈。
PEP 292 将 Template
类添加到 string
模块中,该类使用 $
来指示替换
>>> import string
>>> t = string.Template('$page: $title')
>>> t.substitute({'page':2, 'title': 'The Best of Times'})
'2: The Best of Times'
如果字典中缺少键,substitute()
方法将引发 KeyError
。还有一个 safe_substitute()
方法,它忽略缺少的键
>>> t = string.Template('$page: $title')
>>> t.safe_substitute({'page':3})
'3: $title'
参见
- PEP 292 - 更简单的字符串替换
由 Barry Warsaw 编写和实现。
PEP 318:函数和方法的装饰器¶
Python 2.2 通过添加静态方法和类方法扩展了 Python 的对象模型,但它没有扩展 Python 的语法来提供任何定义静态或类方法的新方式。相反,您必须像往常一样编写 def
语句,并将生成的方法传递给 staticmethod()
或 classmethod()
函数,该函数会将该函数包装为新类型的方法。您的代码看起来像这样
class C:
def meth (cls):
...
meth = classmethod(meth) # Rebind name to wrapped-up class method
如果方法很长,很容易错过或忘记函数体后面的 classmethod()
调用。
最初的意图是添加一些语法来使此类定义更具可读性,但在 2.2 发布时,好的语法并不明显。今天好的语法 仍然 不明显,但用户要求更轻松地访问该功能;已经添加了一个新的语法功能来满足这一需求。
新功能称为“函数装饰器”。这个名字来源于这样的想法:classmethod()
、staticmethod()
和类似函数正在函数对象上存储额外信息;它们正在用更多细节“装饰”函数。
该符号借鉴了 Java,并使用 '@'
字符作为指示符。使用新语法,上面的示例将写成
class C:
@classmethod
def meth (cls):
...
@classmethod
是 meth=classmethod(meth)
赋值的缩写。更一般地,如果你有以下代码
@A
@B
@C
def f ():
...
它等价于以下装饰器前的代码
def f(): ...
f = A(B(C(f)))
装饰器必须在函数定义之前的行上,每行一个装饰器,并且不能与 def 语句在同一行,这意味着 @A def f(): ...
是非法的。您只能装饰函数定义,无论是在模块级别还是在类内部;您不能装饰类定义。
装饰器只是一个函数,它以要装饰的函数作为参数,并返回相同的函数或一些新对象。装饰器的返回值不需要是可调用的(尽管通常是),除非进一步的装饰器将应用于结果。编写自己的装饰器很容易。以下简单示例只在函数对象上设置一个属性
>>> def deco(func):
... func.attr = 'decorated'
... return func
...
>>> @deco
... def f(): pass
...
>>> f
<function f at 0x402ef0d4>
>>> f.attr
'decorated'
>>>
作为一个稍微更现实的例子,以下装饰器检查提供的参数是否为整数
def require_int (func):
def wrapper (arg):
assert isinstance(arg, int)
return func(arg)
return wrapper
@require_int
def p1 (arg):
print arg
@require_int
def p2(arg):
print arg*2
PEP 318 中的一个例子包含了这种思想的一个更高级的版本,它允许您同时指定所需的类型并检查返回的类型。
装饰器函数可以接受参数。如果提供了参数,您的装饰器函数将仅使用这些参数被调用,并且必须返回一个新的装饰器函数;此函数必须接受一个函数并返回一个函数,如前所述。换句话说,@A @B @C(args)
变为
def f(): ...
_deco = C(args)
f = A(B(_deco(f)))
正确理解这一点可能有点费脑,但也不是太难。
一个相关的微小变化使函数的 func_name
属性可写。此属性用于在回溯中显示函数名称,因此装饰器应更改构造并返回的任何新函数的名称。
参见
- PEP 318 - 函数、方法和类的装饰器
由 Kevin D. Smith、Jim Jewett 和 Skip Montanaro 撰写。几个人编写了实现函数装饰器的补丁,但实际签入的是 Mark Russell 编写的补丁 #979728。
- https://wiki.python.org/moin/PythonDecoratorLibrary
此 Wiki 页面包含几个装饰器的示例。
PEP 322:反向迭代¶
一个新的内置函数 reversed(seq)
,它接受一个序列并返回一个以相反顺序遍历序列元素的迭代器。
>>> for i in reversed(xrange(1,4)):
... print i
...
3
2
1
与扩展切片(例如 range(1,4)[::-1]
)相比,reversed()
更易读,运行速度更快,并且使用的内存明显更少。
请注意,reversed()
只接受序列,不接受任意迭代器。如果您想反转迭代器,请先使用 list()
将其转换为列表。
>>> input = open('/etc/passwd', 'r')
>>> for line in reversed(list(input)):
... print line
...
root:*:0:0:System Administrator:/var/root:/bin/tcsh
...
参见
- PEP 322 - 反向迭代
由 Raymond Hettinger 编写和实现。
PEP 324:新 subprocess 模块¶
标准库提供了多种执行子进程的方法,提供了不同的功能和不同级别的复杂性。 os.system(command)
易于使用,但速度慢(它运行一个执行命令的 shell 进程)且危险(您必须小心转义 shell 的元字符)。 popen2
模块提供了可以从子进程捕获标准输出和标准错误的类,但命名令人困惑。 subprocess
模块解决了这个问题,提供了一个统一的接口,提供您可能需要的所有功能。
与 popen2
的类集合不同,subprocess
包含一个名为 subprocess.Popen
的单个类,其构造函数支持许多不同的关键字参数。
class Popen(args, bufsize=0, executable=None,
stdin=None, stdout=None, stderr=None,
preexec_fn=None, close_fds=False, shell=False,
cwd=None, env=None, universal_newlines=False,
startupinfo=None, creationflags=0):
args 通常是字符串序列,将作为执行的子进程的参数。(如果 shell 参数为真,args 可以是一个字符串,然后会传递给 shell 进行解释,就像 os.system()
所做的那样。)
stdin、stdout 和 stderr 指定子进程的输入、输出和错误流。您可以提供文件对象或文件描述符,也可以使用常量 subprocess.PIPE
在子进程和父进程之间创建管道。
构造函数有许多便捷选项
close_fds 请求在运行子进程之前关闭所有文件描述符。
cwd 指定子进程将执行的工作目录(默认为父进程的工作目录)。
env 是一个指定环境变量的字典。
preexec_fn 是在子进程启动之前调用的函数。
universal_newlines 使用 Python 的通用换行符功能打开子进程的输入和输出。
创建 Popen
实例后,您可以调用其 wait()
方法暂停直到子进程退出,调用 poll()
检查它是否退出而无需暂停,或者调用 communicate(data)
将字符串 data 发送到子进程的标准输入。然后 communicate(data)
读取子进程已发送到其标准输出或标准错误的任何数据,返回一个元组 (stdout_data, stderr_data)
。
call()
是一个快捷方式,它将其参数传递给 Popen
构造函数,等待命令完成,并返回子进程的状态码。它可以作为 os.system()
的更安全的替代品
sts = subprocess.call(['dpkg', '-i', '/tmp/new-package.deb'])
if sts == 0:
# Success
...
else:
# dpkg returned an error
...
命令在不使用 shell 的情况下被调用。如果你真的想使用 shell,你可以添加 shell=True
作为关键字参数并提供一个字符串而不是序列
sts = subprocess.call('dpkg -i /tmp/new-package.deb', shell=True)
PEP 举了几个 shell 和 Python 代码的例子,并展示了如何将它们转换为使用 subprocess
的 Python 代码。强烈建议阅读 PEP 的这一部分。
参见
- PEP 324 - subprocess - 新进程模块
由 Peter Åstrand 编写和实现,并得到了 Fredrik Lundh 等人的协助。
PEP 327:Decimal 数据类型¶
Python 始终支持基于底层 C double
类型的浮点 (FP) 数字作为数据类型。然而,虽然大多数编程语言都提供了浮点类型,但许多人(甚至是程序员)都没有意识到浮点数不能准确表示某些十进制分数。新的 Decimal
类型可以准确表示这些分数,精度限制由用户指定。
为什么需要 Decimal?¶
这些限制源于浮点数使用的表示。浮点数由三个组件组成
符号,正或负。
尾数,一个单数字二进制数,后跟小数部分。例如,基数 2 记法中的
1.01
是1 + 0/2 + 1/4
,或十进制记法中的 1.25。指数,表示所表示数字中小数点的位置。
例如,数字 1.25 具有正号,尾数值为 1.01(二进制),指数为 0(小数点不需要移动)。数字 5 具有相同的符号和尾数,但指数为 2,因为尾数乘以 4(2 的指数 2 次方);1.25 * 4 等于 5。
现代系统通常提供符合名为 IEEE 754 标准的浮点支持。C 的 double
类型通常实现为 64 位 IEEE 754 数字,它使用 52 位空间作为尾数。这意味着数字只能指定到 52 位精度。如果您尝试表示无限重复的数字,则展开将在 52 位后截断。不幸的是,大多数软件需要以 10 为基数生成输出,而以 10 为基数的常见分数在二进制中通常是重复小数。例如,十进制 1.1 是二进制 1.0001100110011 ...
;.1 = 1/16 + 1/32 + 1/256 加上无限多个附加项。IEEE 754 必须在 52 位后截断无限重复的小数,因此表示略有不准确。
有时在打印数字时可以看到这种不准确
>>> 1.1
1.1000000000000001
当您打印数字时,不准确性并不总是可见的,因为浮点数到十进制字符串的转换由 C 库提供,大多数 C 库都尝试生成合理的输出。但是,即使不显示,不准确性仍然存在,后续操作可能会放大错误。
对于许多应用程序来说,这并不重要。如果我正在绘制点并将其显示在显示器上,则 1.1 和 1.1000000000000001 之间的差异太小而无法看到。报告通常将输出限制在一定数量的小数位,如果您将数字四舍五入到两位或三位甚至八位小数,则错误永远不会显现。但是,对于重要的应用程序,实现自己的自定义算术例程需要大量工作。
因此,创建了 Decimal
类型。
Decimal
类型¶
一个新的模块 decimal
已添加到 Python 的标准库中。它包含两个类:Decimal
和 Context
。Decimal
实例表示数字,Context
实例用于封装各种设置,例如精度和默认舍入模式。
Decimal
实例是不可变的,就像普通的 Python 整数和浮点数一样;一旦创建,您就无法更改实例表示的值。Decimal
实例可以从整数或字符串创建
>>> import decimal
>>> decimal.Decimal(1972)
Decimal("1972")
>>> decimal.Decimal("1.1")
Decimal("1.1")
您还可以提供包含符号、表示为十进制数字元组的尾数和指数的元组
>>> decimal.Decimal((1, (1, 4, 7, 5), -2))
Decimal("-14.75")
注意:符号位是布尔值,所以 0 是正数,1 是负数。
从浮点数转换会带来一些问题:表示 1.1 的浮点数应该转换为精确的十进制数 1.1,还是转换为 1.1 加上引入的任何不准确性?决定是回避这个问题,并将这种转换排除在 API 之外。相反,您应该使用所需的精度将浮点数转换为字符串,并将字符串传递给 Decimal
构造函数
>>> f = 1.1
>>> decimal.Decimal(str(f))
Decimal("1.1")
>>> decimal.Decimal('%.12f' % f)
Decimal("1.100000000000")
一旦您拥有 Decimal
实例,就可以对它们执行常见的数学运算。一个限制:幂运算需要整数指数
>>> a = decimal.Decimal('35.72')
>>> b = decimal.Decimal('1.73')
>>> a+b
Decimal("37.45")
>>> a-b
Decimal("33.99")
>>> a*b
Decimal("61.7956")
>>> a/b
Decimal("20.64739884393063583815028902")
>>> a ** 2
Decimal("1275.9184")
>>> a**b
Traceback (most recent call last):
...
decimal.InvalidOperation: x ** (non-integer)
您可以将 Decimal
实例与整数组合,但不能与浮点数组合
>>> a + 4
Decimal("39.72")
>>> a + 4.5
Traceback (most recent call last):
...
TypeError: You can interact Decimal only with int, long or Decimal data types.
>>>
Decimal
数字可以与 math
和 cmath
模块一起使用,但请注意,在执行操作之前,它们会立即转换为浮点数,这可能会导致精度和准确性损失。您还将获得一个普通的浮点数,而不是 Decimal
。
>>> import math, cmath
>>> d = decimal.Decimal('123456789012.345')
>>> math.sqrt(d)
351364.18288201344
>>> cmath.sqrt(-d)
351364.18288201344j
Decimal
实例有一个 sqrt()
方法,它返回一个 Decimal
,但是如果你需要其他东西,例如三角函数,你将不得不自己实现它们。
>>> d.sqrt()
Decimal("351364.1828820134592177245001")
Context
类型¶
Context
类的实例封装了十进制运算的多个设置
prec
是精度,即小数位数。rounding
指定舍入模式。decimal
模块有各种可能性的常量:ROUND_DOWN
、ROUND_CEILING
、ROUND_HALF_EVEN
和其他各种常量。traps
是一个字典,指定在遇到某些错误条件时会发生什么:要么引发异常,要么返回一个值。错误条件的一些示例是除零、精度丢失和溢出。
通过调用 getcontext()
可以获得一个线程本地的默认上下文;您可以更改此上下文的属性以更改默认精度、舍入或陷阱处理。以下示例显示了更改默认上下文精度的效果
>>> decimal.getcontext().prec
28
>>> decimal.Decimal(1) / decimal.Decimal(7)
Decimal("0.1428571428571428571428571429")
>>> decimal.getcontext().prec = 9
>>> decimal.Decimal(1) / decimal.Decimal(7)
Decimal("0.142857143")
错误条件的默认操作是可选的;模块可以返回一个特殊值,例如无穷大或非数字,或者可以引发异常
>>> decimal.Decimal(1) / decimal.Decimal(0)
Traceback (most recent call last):
...
decimal.DivisionByZero: x / 0
>>> decimal.getcontext().traps[decimal.DivisionByZero] = False
>>> decimal.Decimal(1) / decimal.Decimal(0)
Decimal("Infinity")
>>>
Context
实例还具有用于格式化数字的各种方法,例如 to_eng_string()
和 to_sci_string()
。
有关更多信息,请参阅 decimal
模块的文档,其中包括快速入门教程和参考。
参见
- PEP 327 - Decimal 数据类型
由 Facundo Batista 撰写,并由 Facundo Batista、Eric Price、Raymond Hettinger、Aahz 和 Tim Peters 实现。
- http://www.lahey.com/float.htm
本文使用 Fortran 代码来说明浮点不准确可能导致的许多问题。
- https://speleotrove.com/decimal/
对基于十进制表示的描述。这种表示正在被提议为标准,并且是新的 Python 十进制类型的基础。大部分材料由 Rexx 语言的设计者 Mike Cowlishaw 撰写。
PEP 328:多行导入¶
一个语言变化是一个小的语法调整,旨在使从模块导入多个名称变得更容易。在 from module import names
语句中,names 是一个逗号分隔的名称序列。如果序列很长,您可以从同一个模块导入多个,或者可以使用反斜杠转义行尾,如下所示
from SimpleXMLRPCServer import SimpleXMLRPCServer,\
SimpleXMLRPCRequestHandler,\
CGIXMLRPCRequestHandler,\
resolve_dotted_attribute
Python 2.4 中的语法更改只是允许将名称放在括号中。Python 忽略括号内的换行符,因此不再需要反斜杠
from SimpleXMLRPCServer import (SimpleXMLRPCServer,
SimpleXMLRPCRequestHandler,
CGIXMLRPCRequestHandler,
resolve_dotted_attribute)
PEP 还提议所有 import
语句都是绝对导入,以一个前导 .
字符表示相对导入。PEP 的这部分内容没有在 Python 2.4 中实现,但在 Python 2.5 中完成了。
参见
- PEP 328 - Imports: Multi-Line and Absolute/Relative
由 Aahz 撰写。多行导入由 Dima Dorfman 实现。
PEP 331:独立于区域设置的浮点/字符串转换¶
locale
模块允许 Python 软件选择各种本地化到特定国家或语言的转换和显示约定。但是,该模块小心地不更改数字区域设置,因为 Python 实现中的各种函数要求数字区域设置保持设置为 'C'
区域设置。通常这是因为代码使用了 C 库的 atof()
函数。
但是,不设置数字区域设置给使用第三方 C 库的扩展带来了麻烦,因为它们没有设置正确的区域设置。激励性的例子是 GTK+,它的用户界面小部件没有以当前区域设置显示数字。
PEP 中描述的解决方案是向 Python API 添加三个执行仅 ASCII 转换的新函数,忽略区域设置
PyOS_ascii_strtod(str, ptr)
和PyOS_ascii_atof(str, ptr)
都将字符串转换为 Cdouble
。PyOS_ascii_formatd(buffer, buf_len, format, d)
将一个double
转换为 ASCII 字符串。
这些函数的代码来自 GLib 库(https://developer-old.gnome.org/glib/2.26/),其开发者慷慨地重新许可了相关函数并将其捐赠给 Python 软件基金会。locale
模块现在可以更改数字区域设置,从而允许 GTK+ 等扩展产生正确的结果。
参见
- PEP 331 - 独立于区域设置的浮点/字符串转换
由 Christian R. Reis 撰写,Gustavo Carneiro 实现。
其他语言更改¶
以下是 Python 2.4 对核心 Python 语言的所有更改。
添加了函数和方法的装饰器(PEP 318)。
添加了内置的
set()
和frozenset()
类型(PEP 218)。其他新的内置函数包括reversed(seq)
函数(PEP 322)。添加了生成器表达式(PEP 289)。
某些数值表达式不再返回限制为 32 位或 64 位的值(PEP 237)。
现在可以在
from module import names
语句中,在名称列表周围加上括号(PEP 328)。dict.update()
方法现在接受与dict
构造函数相同的参数形式。这包括任何映射、任何键/值对的可迭代对象以及关键字参数。(由 Raymond Hettinger 贡献。)字符串方法
ljust()
、rjust()
和center()
现在接受一个可选参数,用于指定除空格以外的填充字符。(由 Raymond Hettinger 贡献。)字符串还新增了
rsplit()
方法,其功能与split()
方法类似,但从字符串末尾开始拆分。(由 Sean Reifschneider 贡献。)>>> 'www.python.org'.split('.', 1) ['www', 'python.org'] 'www.python.org'.rsplit('.', 1) ['www.python', 'org']
列表的
sort()
方法增加了三个关键字参数:cmp、key 和 reverse。这些参数使sort()
的一些常见用法更简单。所有这些参数都是可选的。对于 cmp 参数,其值应该是一个比较函数,该函数接受两个参数并根据它们的比较结果返回 -1、0 或 +1。然后将使用此函数对列表进行排序。以前,这是唯一可以提供给
sort()
的参数。key 应该是一个单参数函数,它接受一个列表元素并返回该元素的比较键。然后使用比较键对列表进行排序。以下示例对列表进行不区分大小写的排序
>>> L = ['A', 'b', 'c', 'D'] >>> L.sort() # Case-sensitive sort >>> L ['A', 'D', 'b', 'c'] >>> # Using 'key' parameter to sort list >>> L.sort(key=lambda x: x.lower()) >>> L ['A', 'b', 'c', 'D'] >>> # Old-fashioned way >>> L.sort(cmp=lambda x,y: cmp(x.lower(), y.lower())) >>> L ['A', 'b', 'c', 'D']
最后一个示例使用 cmp 参数,是执行不区分大小写排序的旧方法。它有效但比使用 key 参数慢。使用 key 为列表中的每个元素调用一次
lower()
方法,而使用 cmp 将为每次比较调用两次,因此使用 key 可以节省lower()
方法的调用次数。对于简单的键函数和比较函数,通常可以通过使用非绑定方法来避免
lambda
表达式。例如,上述不区分大小写的排序最好写成>>> L.sort(key=str.lower) >>> L ['A', 'b', 'c', 'D']
最后,reverse 参数接受一个布尔值。如果该值为真,则列表将按相反顺序排序。现在,您可以编写
L.sort(reverse=True)
而不是L.sort(); L.reverse()
。排序结果现在保证是稳定的。这意味着具有相同键的两个条目将按照它们输入的相同顺序返回。例如,您可以按名称对人员列表进行排序,然后按年龄对列表进行排序,从而得到一个按年龄排序的列表,其中年龄相同的人员按名称排序。
(对
sort()
的所有更改均由 Raymond Hettinger 贡献。)新增了一个内置函数
sorted(iterable)
,它的工作方式与原地list.sort()
方法类似,但可以在表达式中使用。区别在于输入可以是任何可迭代对象;
对新形成的副本进行排序,保持原始副本不变;并且
表达式返回新的已排序副本
>>> L = [9,7,8,3,2,4,1,6,5] >>> [10+i for i in sorted(L)] # usable in a list comprehension [11, 12, 13, 14, 15, 16, 17, 18, 19] >>> L # original is left unchanged [9,7,8,3,2,4,1,6,5] >>> sorted('Monty Python') # any iterable may be an input [' ', 'M', 'P', 'h', 'n', 'n', 'o', 'o', 't', 't', 'y', 'y'] >>> # List the contents of a dict sorted by key values >>> colormap = dict(red=1, blue=2, green=3, black=4, yellow=5) >>> for k, v in sorted(colormap.iteritems()): ... print k, v ... black 4 blue 2 green 3 red 1 yellow 5
(由 Raymond Hettinger 贡献。)
整数操作将不再触发
OverflowWarning
。OverflowWarning
警告将在 Python 2.5 中消失。解释器新增了一个开关
-m
,它接受一个名称,在sys.path
中搜索相应的模块,并将该模块作为脚本运行。例如,您现在可以使用python -m profile
运行 Python 分析器。(由 Nick Coghlan 贡献。)eval(expr, globals, locals)
和execfile(filename, globals, locals)
函数以及exec
语句现在接受任何映射类型作为 locals 参数。以前这必须是常规的 Python 字典。(由 Raymond Hettinger 贡献。)内置函数
zip()
和itertools.izip()
在不带参数调用时现在返回一个空列表。以前它们会引发TypeError
异常。这使得它们更适合与可变长度参数列表一起使用>>> def transpose(array): ... return zip(*array) ... >>> transpose([(1,2,3), (4,5,6)]) [(1, 4), (2, 5), (3, 6)] >>> transpose([]) []
(由 Raymond Hettinger 贡献。)
在导入模块时遇到故障不再会在
sys.modules
中留下部分初始化的模块对象。留下不完整的模块对象会欺骗后续导入同一模块的操作成功,从而导致令人困惑的错误。(由 Tim Peters 修复。)None
现在是一个常量;将新值绑定到名称None
的代码现在是语法错误。(由 Raymond Hettinger 贡献。)
优化¶
列表和元组切片的内部循环经过优化,现在运行速度提高了约三分之一。字典的内部循环也经过优化,从而提高了
keys()
、values()
、items()
、iterkeys()
、itervalues()
和iteritems()
的性能。(由 Raymond Hettinger 贡献。)列表增长和收缩的机制已针对速度和空间效率进行了优化。由于更高效的代码路径和更少使用底层系统
realloc()
,从列表中添加和弹出元素现在运行速度更快。列表推导式也受益。list.extend()
也得到了优化,不再在其参数转换为临时列表后才扩展基本列表。(由 Raymond Hettinger 贡献。)list()
、tuple()
、map()
、filter()
和zip()
在使用提供__len__()
方法的非序列参数时,现在运行速度提高了数倍。(由 Raymond Hettinger 贡献。)方法
list.__getitem__()
、dict.__getitem__()
和dict.__contains__()
现在实现为method_descriptor
对象,而不是wrapper_descriptor
对象。这种形式的访问使它们的性能翻倍,并使它们更适合用作函数式参数:map(mydict.__getitem__, keylist)
。(由 Raymond Hettinger 贡献。)新增了一个操作码
LIST_APPEND
,它简化了列表推导式生成的字节码,并将其速度提高了约三分之一。(由 Raymond Hettinger 贡献。)窥孔字节码优化器已得到改进,可生成更短、更快的字节码;值得注意的是,生成的字节码更具可读性。(由 Raymond Hettinger 增强。)
形式为
s = s + "abc"
和s += "abc"
的字符串连接在某些情况下现在执行效率更高。此优化不会出现在其他 Python 实现中,例如 Jython,因此您不应依赖它;当您希望高效地将大量字符串粘合在一起时,仍然建议使用字符串的join()
方法。(由 Armin Rigo 贡献。)
2.4 优化的最终结果是 Python 2.4 运行 pystone 基准测试的速度比 Python 2.3 快约 5%,比 Python 2.2 快 35%。(pystone 不是一个特别好的基准测试,但它是最常用的 Python 性能衡量标准。您自己的应用程序可能从 Python 2.4 中获得更大或更小的收益。)
新的、改进的和已弃用的模块¶
一如既往,Python 的标准库获得了许多增强和错误修复。以下是一些最显著变化的局部列表,按模块名称按字母顺序排序。有关更完整的更改列表,请查阅源代码树中的 Misc/NEWS
文件,或查看 CVS 日志以获取所有详细信息。
asyncore
模块的loop()
函数现在有一个 count 参数,允许您执行有限次数的轮询循环。默认仍然是无限循环。base64
模块现在更完整地支持 RFC 3548 中的 Base64、Base32 和 Base16 编码和解码,包括可选的大小写折叠和可选的替代字母表。(由 Barry Warsaw 贡献。)bisect
模块现在有了底层的 C 实现,以提高性能。(由 Dmitry Vasiliev 贡献。)由 Hye-Shik Chang 维护的 CJKCodecs 东亚编码集合已集成到 2.4 中。新的编码包括
中文(中华人民共和国):gb2312、gbk、gb18030、big5hkscs、hz
中文(中华民国):big5、cp950
- 日文:cp932、euc-jis-2004、euc-jp、euc-jisx0213、iso-2022-jp、
iso-2022-jp-1、iso-2022-jp-2、iso-2022-jp-3、iso-2022-jp-ext、iso-2022-jp-2004、shift-jis、shift-jisx0213、shift-jis-2004
韩文:cp949、euc-kr、johab、iso-2022-kr
还添加了一些其他新编码:HP Roman8、ISO_8859-11、ISO_8859-16、PCTP-154 和 TIS-620。
UTF-8 和 UTF-16 编解码器现在能更好地处理接收部分输入。以前,
StreamReader
类会尝试读取更多数据,使得从流恢复解码变得不可能。read()
方法现在将返回尽可能多的数据,并且未来的调用将从上次停止的地方恢复解码。(由 Walter Dörwald 实现。)新增了一个
collections
模块,用于各种专门的集合数据类型。目前它只包含一种类型deque
,一个双端队列,支持高效地从两端添加和删除元素>>> from collections import deque >>> d = deque('ghi') # make a new deque with three items >>> d.append('j') # add a new entry to the right side >>> d.appendleft('f') # add a new entry to the left side >>> d # show the representation of the deque deque(['f', 'g', 'h', 'i', 'j']) >>> d.pop() # return and remove the rightmost item 'j' >>> d.popleft() # return and remove the leftmost item 'f' >>> list(d) # list the contents of the deque ['g', 'h', 'i'] >>> 'h' in d # search the deque True
现在,
Queue
和threading
等几个模块利用collections.deque
来提高性能。(由 Raymond Hettinger 贡献。)ConfigParser
类得到了一些增强。read()
方法现在返回成功解析的文件列表,如果传递的 value 参数不是字符串,set()
方法会引发TypeError
。(由 John Belmonte 和 David Goodger 贡献。)curses
模块现在支持 ncurses 扩展use_default_colors()
。在终端支持透明度的平台上,这使得可以使用透明背景。(由 Jörg Lehmann 贡献。)difflib
模块现在包含一个HtmlDiff
类,该类创建一个 HTML 表格,并排显示文本的两个版本。(由 Dan Gass 贡献。)email
包已更新到版本 3.0,该版本放弃了各种已弃用的 API,并取消了对 2.3 之前 Python 版本的支持。该包的 3.0 版本使用了一种新的增量 MIME 消息解析器,可在email.FeedParser
模块中找到。新解析器不需要将整个消息读入内存,并且如果消息格式不正确也不会引发异常;相反,它会将任何问题记录在消息的defect
属性中。(由 Anthony Baxter、Barry Warsaw、Thomas Wouters 等人开发。)heapq
模块已转换为 C 语言。由此带来的十倍速度提升使得该模块适用于处理大量数据。此外,该模块还有两个新函数nlargest()
和nsmallest()
,它们使用堆在不完全排序的情况下查找数据集中最大的 N 个值或最小的 N 个值。(由 Raymond Hettinger 贡献。)httplib
模块现在包含各种与 HTTP 相关的 RFC 文档中定义的 HTTP 状态码常量。常量名称包括OK
、CREATED
、CONTINUE
和MOVED_PERMANENTLY
;使用 pydoc 获取完整列表。(由 Andrew Eland 贡献。)imaplib
模块现在支持 IMAP 的 THREAD 命令(由 Yves Dionne 贡献)以及新的deleteacl()
和myrights()
方法(由 Arnaud Mazin 贡献)。itertools
模块新增了groupby(iterable[, *func*])
函数。iterable 是可以迭代以返回元素流的对象,可选的 func 参数是一个函数,它接受一个元素并返回一个键值;如果省略,则键就是元素本身。groupby()
然后将元素分组为具有匹配键值的子序列,并返回一系列包含键值和子序列迭代器的 2 元组。下面是一个例子,使其更清晰。key 函数只是简单地返回一个数字是奇数还是偶数,因此
groupby()
的结果是返回连续的奇数或偶数序列。>>> import itertools >>> L = [2, 4, 6, 7, 8, 9, 11, 12, 14] >>> for key_val, it in itertools.groupby(L, lambda x: x % 2): ... print key_val, list(it) ... 0 [2, 4, 6] 1 [7] 0 [8] 1 [9, 11] 0 [12, 14] >>>
groupby()
通常与已排序的输入一起使用。groupby()
的逻辑类似于 Unix 的uniq
过滤器,这使得它非常方便用于消除、计数或识别重复元素>>> word = 'abracadabra' >>> letters = sorted(word) # Turn string into a sorted list of letters >>> letters ['a', 'a', 'a', 'a', 'a', 'b', 'b', 'c', 'd', 'r', 'r'] >>> for k, g in itertools.groupby(letters): ... print k, list(g) ... a ['a', 'a', 'a', 'a', 'a'] b ['b', 'b'] c ['c'] d ['d'] r ['r', 'r'] >>> # List unique letters >>> [k for k, g in groupby(letters)] ['a', 'b', 'c', 'd', 'r'] >>> # Count letter occurrences >>> [(k, len(list(g))) for k, g in groupby(letters)] [('a', 5), ('b', 2), ('c', 1), ('d', 1), ('r', 2)]
(由 Hye-Shik Chang 贡献。)
itertools
还新增了一个名为tee(iterator, N)
的函数,它返回 N 个独立的迭代器,它们复制 iterator。如果省略 N,默认值为 2。>>> L = [1,2,3] >>> i1, i2 = itertools.tee(L) >>> i1,i2 (<itertools.tee object at 0x402c2080>, <itertools.tee object at 0x402c2090>) >>> list(i1) # Run the first iterator to exhaustion [1, 2, 3] >>> list(i2) # Run the second iterator to exhaustion [1, 2, 3]
请注意,
tee()
必须保留迭代器返回的值的副本;在最坏的情况下,它可能需要保留所有副本。因此,如果领先迭代器在一个长输入流中远远领先于跟随迭代器,则应谨慎使用。如果分离很大,那么您不妨使用list()
。当迭代器彼此密切跟踪时,tee()
是理想的选择。可能的应用包括书签、窗口或前瞻迭代器。(由 Raymond Hettinger 贡献。)locale
模块添加了许多函数,例如bind_textdomain_codeset()
用于指定特定编码,以及一系列l*gettext()
函数,它们以选定的编码返回消息。(由 Gustavo Niemeyer 贡献。)一些关键字参数已添加到
logging
包的basicConfig()
函数中,以简化日志配置。默认行为是将消息记录到标准错误,但可以指定各种关键字参数来记录到特定文件、更改日志格式或设置日志级别。例如import logging logging.basicConfig(filename='/var/log/application.log', level=0, # Log all messages format='%(levelname):%(process):%(thread):%(message)')
logging
包的其他新增功能包括一个方便的log(level, msg)
方法,以及一个TimedRotatingFileHandler
类,它以定时间隔轮换其日志文件。该模块已经有RotatingFileHandler
,它会在文件超出特定大小时轮换日志。这两个类都派生自一个新的BaseRotatingHandler
类,该类可用于实现其他轮换处理程序。(更改由 Vinay Sajip 实现。)
marshal
模块现在在解包数据结构时共享内部化字符串。这可能会缩小某些 pickle 字符串的大小,但主要效果是使.pyc
文件显著减小。(由 Martin von Löwis 贡献。)nntplib
模块的NNTP
类增加了description()
和descriptions()
方法,用于检索单个新闻组或一组新闻组的新闻组描述。(由 Jürgen A. Erhard 贡献。)operator
模块新增了两个函数,attrgetter(attr)
和itemgetter(index)
。这两个函数都返回一个可调用对象,该对象接受一个参数并返回相应的属性或项;当与map()
或sorted()
一起使用时,这些可调用对象是出色的数据提取器。例如>>> L = [('c', 2), ('d', 1), ('a', 4), ('b', 3)] >>> map(operator.itemgetter(0), L) ['c', 'd', 'a', 'b'] >>> map(operator.itemgetter(1), L) [2, 1, 4, 3] >>> sorted(L, key=operator.itemgetter(1)) # Sort list by second tuple item [('d', 1), ('c', 2), ('b', 3), ('a', 4)]
(由 Raymond Hettinger 贡献。)
optparse
模块以各种方式进行了更新。该模块现在将其消息通过gettext.gettext()
传递,从而可以实现 Optik 帮助和错误消息的国际化。选项的帮助消息现在可以包含字符串'%default'
,该字符串将替换为选项的默认值。(由 Greg Ward 贡献。)长期计划是在未来的某个 Python 版本中弃用
rfc822
模块,转而使用email
包。为此,email.Utils.formatdate
函数已更改,使其可用作rfc822.formatdate()
的替代品。您可能希望在编写新的电子邮件处理代码时考虑这一点。(更改由 Anthony Baxter 实现。)os
模块新增了urandom(n)
函数,该函数返回一个包含 n 字节随机数据的字符串。此函数提供对特定平台随机源的访问,例如 Linux 上的/dev/urandom
或 Windows CryptoAPI。(由 Trevor Perrin 贡献。)另一个新函数:
os.path.lexists(path)
如果 path 指定的文件存在,则返回 true,无论它是否是符号链接。这与现有的os.path.exists(path)
函数不同,后者如果 path 是一个指向不存在目的地的符号链接,则返回 false。(由 Beni Cherniavsky 贡献。)poplib
模块现在支持 POP over SSL。(由 Hector Urtubia 贡献。)profile
模块现在可以分析 C 扩展函数。(由 Nick Bastin 贡献。)random
模块有一个名为getrandbits(N)
的新方法,该方法返回一个长度为 N 位的长整数。现有的randrange()
方法现在在适当的情况下使用getrandbits()
,从而使任意大随机数的生成更有效。(由 Raymond Hettinger 贡献。)re
模块接受的正则表达式语言通过简单的条件表达式进行了扩展,写为(?(group)A|B)
。group 可以是数字组 ID 或在表达式前面用(?P<group>...)
定义的组名。如果指定的组匹配,则正则表达式模式 A 将与字符串进行测试;如果组不匹配,则将使用模式 B。(由 Gustavo Niemeyer 贡献。)re
模块也不再是递归的,这要归功于 Gustavo Niemeyer 的大量工作。在递归正则表达式引擎中,某些模式会导致消耗大量的 C 堆栈空间,并且可能会导致堆栈溢出。例如,如果您将 30000 字节的a
字符字符串与表达式(a|b)+
进行匹配,则每个字符都会消耗一个堆栈帧。Python 2.3 试图检查堆栈溢出并引发RuntimeError
异常,但某些模式可能会绕过检查,如果您运气不好,Python 可能会出现段错误。Python 2.4 的正则表达式引擎可以毫无问题地匹配此模式。signal
模块现在对signal.signal()
函数的参数执行更严格的错误检查。例如,您不能在SIGKILL
信号上设置处理程序;以前的 Python 版本会默默接受这一点,但 2.4 将引发RuntimeError
异常。socket
模块新增了两个函数。socketpair()
返回一对已连接的套接字,getservbyport(port)
查找给定端口号的服务名。(由 Dave Cole 和 Barry Warsaw 贡献。)sys.exitfunc()
函数已被弃用。代码应使用现有的atexit
模块,该模块正确处理调用多个退出函数。最终sys.exitfunc()
将成为一个纯粹的内部接口,仅由atexit
访问。tarfile
模块现在默认生成 GNU 格式的 tar 文件。(由 Lars Gustäbel 贡献。)threading
模块现在提供了一种优雅简单的方式来支持线程本地数据。该模块包含一个local
类,其属性值对不同的线程是本地的。import threading data = threading.local() data.number = 42 data.url = ('www.python.org', 80)
其他线程可以为
number
和url
属性分配和检索它们自己的值。您可以子类化local
以初始化属性或添加方法。(由 Jim Fulton 贡献。)timeit
模块现在在计时循环期间自动禁用周期性垃圾回收。此更改使连续计时更具可比性。(由 Raymond Hettinger 贡献。)weakref
模块现在支持更广泛的对象,包括 Python 函数、类实例、集合、不可变集合、双端队列、数组、文件、套接字和正则表达式模式对象。(由 Raymond Hettinger 贡献。)xmlrpclib
模块现在支持多调用扩展,用于在单个 HTTP 操作中传输多个 XML-RPC 调用。(由 Brian Quinlan 贡献。)mpz
、rotor
和xreadlines
模块已被删除。
doctest¶
由于 Edward Loper 和 Tim Peters 的大量重构,doctest
模块进行了相当大的重构。测试仍然可以像运行 doctest.testmod()
一样简单,但重构允许以各种方式自定义模块的操作
新的 DocTestFinder
类从给定对象的文档字符串中提取测试
def f (x, y):
""">>> f(2,2)
4
>>> f(3,2)
6
"""
return x*y
finder = doctest.DocTestFinder()
# Get list of DocTest instances
tests = finder.find(f)
新的 DocTestRunner
类然后运行单个测试并可以生成结果摘要
runner = doctest.DocTestRunner()
for t in tests:
tried, failed = runner.run(t)
runner.summarize(verbose=1)
上面的例子产生以下输出
1 items passed all tests:
2 tests in f
2 tests in 1 items.
2 passed and 0 failed.
Test passed.
DocTestRunner
使用 OutputChecker
类的实例来比较预期输出和实际输出。此类别接受许多不同的标志来定制其行为;有抱负的用户还可以编写一个全新的 OutputChecker
子类。
默认的输出检查器提供了许多方便的功能。例如,使用 doctest.ELLIPSIS
选项标志,预期输出中的省略号 (...
) 匹配任何子字符串,从而更容易适应在细微方面有所不同的输出
def o (n):
""">>> o(1)
<__main__.C instance at 0x...>
>>>
"""
另一个特殊字符串,<BLANKLINE>
,匹配一个空行
def p (n):
""">>> p(1)
<BLANKLINE>
>>>
"""
另一个新功能是通过指定 doctest.REPORT_UDIFF
(统一差异)、doctest.REPORT_CDIFF
(上下文差异)或 doctest.REPORT_NDIFF
(增量样式)选项标志来生成差异样式输出显示。例如
def g (n):
""">>> g(4)
here
is
a
lengthy
>>>"""
L = 'here is a rather lengthy list of words'.split()
for word in L[:n]:
print word
使用指定的 doctest.REPORT_UDIFF
运行上述函数的测试,您将获得以下输出
**********************************************************************
File "t.py", line 15, in g
Failed example:
g(4)
Differences (unified diff with -expected +actual):
@@ -2,3 +2,3 @@
is
a
-lengthy
+rather
**********************************************************************
构建和 C API 更改¶
Python 构建过程和 C API 的一些更改是
添加了三个新的方便宏,用于扩展函数的常见返回值:
Py_RETURN_NONE
、Py_RETURN_TRUE
和Py_RETURN_FALSE
。(由 Brett Cannon 贡献。)另一个新宏
Py_CLEAR
减少了 obj 的引用计数并将 obj 设置为 null 指针。(由 Jim Fulton 贡献。)一个新函数
PyTuple_Pack(N, obj1, obj2, ..., objN)
,从可变长度参数列表的 Python 对象构建元组。(由 Raymond Hettinger 贡献。)一个新函数
PyDict_Contains(d, k)
,实现了快速字典查找,而不会掩盖查找过程中引发的异常。(由 Raymond Hettinger 贡献。)如果浮点或双精度参数 X 是 NaN,Py_IS_NAN(X) 宏返回 1。(由 Tim Peters 贡献。)
C 代码可以通过使用新的
PyEval_ThreadsInitialized()
函数来避免不必要的锁定,该函数用于判断是否执行了任何线程操作。如果此函数返回 false,则不需要锁定操作。(由 Nick Coghlan 贡献。)一个新函数
PyArg_VaParseTupleAndKeywords()
与PyArg_ParseTupleAndKeywords()
相同,但它接受va_list
而不是多个参数。(由 Greg Chapman 贡献。)一个新的方法标志
METH_COEXIST
允许在槽中定义的函数与同名的PyCFunction
共存。这可以将set.__contains__()
等方法的访问时间缩短一半。(由 Raymond Hettinger 贡献。)Python 现在可以为解释器本身构建额外的性能分析,旨在帮助开发 Python 核心的人员。向 configure 脚本提供
--enable-profiling
将允许您使用 gprof 对解释器进行性能分析,提供--with-tsc
开关将启用使用奔腾的时间戳计数器寄存器进行性能分析。请注意,--with-tsc
开关名称略有不当,因为性能分析功能也适用于 PowerPC 平台,尽管该处理器架构不将该寄存器称为“TSC 寄存器”。(由 Jeremy Hylton 贡献。)tracebackobject
类型已重命名为PyTracebackObject
。
特定于端口的更改¶
Windows 移植现在在 MSVC++ 7.1 和 6 版本下构建。(由 Martin von Löwis 贡献。)
移植到 Python 2.4¶
本节列出了可能需要更改代码的先前描述的更改
左移和过大的十六进制/八进制常量不再触发
FutureWarning
并返回限制为 32 或 64 位的值;而是返回一个长整数。整数操作将不再触发
OverflowWarning
。OverflowWarning
警告将在 Python 2.5 中消失。如果调用时没有参数,内置函数
zip()
和itertools.izip()
现在返回一个空列表,而不是引发TypeError
异常。您不能再比较
datetime
模块提供的date
和datetime
实例。现在,不同类的两个实例将始终不相等,并且相对比较(<
,>
)将引发TypeError
。dircache.listdir()
现在将异常传递给调用者,而不是返回空列表。LexicalHandler.startDTD()
曾经以错误的顺序接收公共和系统 ID。这已得到纠正;依赖于错误顺序的应用程序需要修复。如果省略了 mutate 参数且相关,
fcntl.ioctl()
现在会发出警告。tarfile
模块现在默认生成 GNU 格式的 tar 文件。在导入模块时遇到失败不再在
sys.modules
中留下部分初始化的模块对象。None
现在是一个常量;将新值绑定到名称None
的代码现在是语法错误。signals.signal()
函数现在对某些非法值引发RuntimeError
异常;以前这些错误会默默地通过。例如,您不能再在SIGKILL
信号上设置处理程序。
致谢¶
作者要感谢以下人员为本文的各种草稿提供了建议、更正和帮助:Koray Can、Hye-Shik Chang、Michael Dyck、Raymond Hettinger、Brian Hurt、Hamish Lawson、Fredrik Lundh、Sean Reifschneider、Sadruddin Rejeb。