Python 3.1 有什么新变化

作者

Raymond Hettinger(译者:wh2099 at outlook dot com)

This article explains the new features in Python 3.1, compared to 3.0.

PEP 372: 有序字典

常规的 Python 字典会以任意顺序迭代键/值对。 多年以来,有好几位作者编写了可以记住键的初始插入顺序的替代实现。 基于这些实现的经验,现在引入了新的 collections.OrderedDict 类。

OrderedDict API 与常规字典基本相同,但将根据每个键首次插入的时间以有保证的顺序来迭代键和值。 如果一个新条目覆盖了现有的条目,则原始插入位置会保持不变。 删除条目并重新插入则会将其移至末尾。

The standard library now supports use of ordered dictionaries in several modules. The configparser module uses them by default. This lets configuration files be read, modified, and then written back in their original order. The _asdict() method for collections.namedtuple() now returns an ordered dictionary with the values appearing in the same order as the underlying tuple indices. The json module is being built-out with an object_pairs_hook to allow OrderedDicts to be built by the decoder. Support was also added for third-party tools like PyYAML.

参见

PEP 372 - 有序字典

PEP 由 Armin Ronacher 和 Raymond Hettinger 撰写,由 Raymond Hettinger 实现。

PEP 378: 千位分隔符的格式说明符

内置的 format() 函数和 str.format() 方法使用的迷你语言现在包括一种以千位分隔符格式化数字的简单、不可感知语言区域的方式。 这提供了更为人性化的程序输出,提升了外观专业程度与可读性:

>>> format(1234567, ',d')
'1,234,567'
>>> format(1234567.89, ',.2f')
'1,234,567.89'
>>> format(12345.6 + 8901234.12j, ',f')
'12,345.600000+8,901,234.120000j'
>>> format(Decimal('1234567.89'), ',f')
'1,234,567.89'

支持的类型有 int, float, complexdecimal.Decimal

目前还在讨论要如何指定替代分隔符如点号、空格、撇号或下划线等。 可感知语言区域的应用程序应当使用现有的支持千位分隔符的 n 格式说明符。

参见

PEP 378 - 千位分隔符的格式说明符

PEP 由 Raymond Hettinger 撰写,并由 Eric Smith 和 Mark Dickinson 实现

其他语言特性修改

对Python 语言核心进行的小改动:

  • 包括 __main__.py 文件的目录和 zip 归档文件现在可通过将其名称传给解释器来直接执行。 这样的目录/zip 文件会自动作为 sys.path 的第一个条目插入。 (由 Andy Chu 提议并编写初始补丁;由 Phillip J. Eby 和 Nick Coghlan 改进补丁;bpo-1739468。)

  • int() 类型增加了一个 bit_length 方法用来返回以二进制代表其参数值所需的比特位数量:

    >>> n = 37
    >>> bin(37)
    '0b100101'
    >>> n.bit_length()
    6
    >>> n = 2**123-1
    >>> n.bit_length()
    123
    >>> (n+1).bit_length()
    124
    

    (由 Fredrik Johansson, Victor Stinner, Raymond Hettinger 和 Mark Dickinson 在 bpo-3439 中贡献。)

  • format() 字符串中的字段现在可被自动编号:

    >>> 'Sir {} of {}'.format('Gallahad', 'Camelot')
    'Sir Gallahad of Camelot'
    

    之前,字符串应当具有所需的编号字段例如: 'Sir {0} of {1}'

    (由 Eric Smith在 bpo-5237 中贡献)

  • string.maketrans() 函数已被弃用并由新的静态方法 bytes.maketrans()bytearray.maketrans() 取代。 这项修改解决了对于 string 模块支持类型的困惑。 现在,str, bytesbytearray 分别拥有它们自己的附带适当类型的中间转写表的 maketranstranslate 方法。

    (由Georg Brandl在 bpo-5675 中贡献)

  • The syntax of the with 语句的语法现在允许单个语句中存在多个上下文管理器:

    >>> with open('mylog.txt') as infile, open('a.out', 'w') as outfile:
    ...     for line in infile:
    ...         if '<critical>' in line:
    ...             outfile.write(line)
    

    有了这个新语法,contextlib.nested() 函数已不再必要因而现在已被弃用。

    (由 Georg Brandl 和 Mattias Brändström 贡献; appspot issue 53094。)

  • 现在 round(x, n)x 为整数时将返回整数。 之前是返回浮点数:

    >>> round(1123, -2)
    1100
    

    (由 Mark Dickinson在 bpo-4707 贡献)

  • Python 现在使用 David Gay 的算法来查找不会改变实际值的最短浮点表示形式。 这应当有助于缓解某些对于二进制浮点数的困惑。

    这项改进的优点对于 1.1 这样无法用二进制浮点精确表示的数来说是很明显的。 由于没有完全等价的表示,float('1.1') 这样的表达式会被求解为最接近的可表示值即十六进制的 0x1.199999999999ap+0 或十进制的 1.100000000000000088817841970012523233890533447265625。 这个最接近的值过去和现在仍然会在后续的浮点运算中被使用。

    新的改变针对的是如何显示数字。 在之前,Python 使用了一种简单的方式。 repr(1.1) 的值会被计算为 format(1.1, '.17g') 并将被求解为 '1.1000000000000001'。 使用 17 个数位的优点是它将凭借 IEEE-754 标准来确保 eval(repr(1.1)) 将恰好被舍入到其原始值。 缺点则是会让许多人感觉这样的输出令人困惑(将二进制浮点表示形式的内在局限性误认为是 Python 本身的问题)。

    用于 repr(1.1) 的新算法更为智能并将返回 '1.1'。 在实际上,它会搜索所有等价的字符串表示形式(使用相同的下层浮点值进行排序)并返回其中最短的表示形式。

    新算法倾向于尽可能放出更为清晰的表示形式,但它并不改变下层的值。 因此仍然会是 1.1 + 2.2 != 3.3 虽然从表示形式上看情况不是这样。

    这个新算法依赖于下层浮点实现的某些特性。 如果未找到所需的特性,则将继续使用旧算法。 此外,文本 pickle 协议也会通过使用旧算法来保证跨平台的可移植性。

    (由 Eric Smith 和 Mark Dickinson 在 bpo-1580 贡献)

新增,改进和弃用的模块

  • 增加了一个 collections.Counter 类以支持方便地统计一个序列或可迭代对象中的唯一条目数量。:

    >>> Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])
    Counter({'blue': 3, 'red': 2, 'green': 1})
    

    (由 Raymond Hettinger 在 bpo-1696199 中贡献。)

  • 半圆了一个新模块 tkinter.ttk 用于访问带主题的 Tk 部件集。 ttk 的基本设计思路,就是尽可能地把实现部件行为的代码与实现其外观的代码分离开来。

    (由 Guilherme Polo 在 bpo-2983 中贡献。)

  • gzip.GzipFilebz2.BZ2File 类现在已支持上下文管理协议:

    >>> # Automatically close file after writing
    >>> with gzip.GzipFile(filename, "wb") as f:
    ...     f.write(b"xxx")
    

    (由 Antoine Pitrou 贡献。)

  • decimal 模块现在支持基于一个二进制 float 来创建 decimal 对象。 转换是准确的但有时也会令人吃惊:

    >>> Decimal.from_float(1.1)
    Decimal('1.100000000000000088817841970012523233890533447265625')
    

    这个长长的 decimal 结果值显示了 1.1 所保存的实际二进制分数。 这个分数有许多位因为 1.1 无法用二进制来精确表示。

    (由Raymond Hettinger 和 Mark Dickinson贡献。)

  • itertools 模块增加了两个新函数。 itertools.combinations_with_replacement() 函数是生成组合数学结果包括排列与笛卡尔积的四个函数之一。 itertools.compress() 函数模仿了 APL 中的同名函数。 此外,现有的 itertools.count() 函数现在有一个可选的 step 参数并可接受任意类型的计数序列包括 fractions.Fractiondecimal.Decimal:

    >>> [p+q for p,q in combinations_with_replacement('LOVE', 2)]
    ['LL', 'LO', 'LV', 'LE', 'OO', 'OV', 'OE', 'VV', 'VE', 'EE']
    
    >>> list(compress(data=range(10), selectors=[0,0,1,1,0,1,0,1,0,0]))
    [2, 3, 5, 7]
    
    >>> c = count(start=Fraction(1,2), step=Fraction(1,6))
    >>> [next(c), next(c), next(c), next(c)]
    [Fraction(1, 2), Fraction(2, 3), Fraction(5, 6), Fraction(1, 1)]
    

    (由 Raymond Hettinger 贡献。)

  • collections.namedtuple() 现在支持关键字参数 rename,它允许将无效的字段名自动转换为 _0, _1 等形式的位置名称。 这在字段名是由外部源如 CSV 标头、SQL 字段列表或用户输入创建的时候会很有用处:

    >>> query = input()
    SELECT region, dept, count(*) FROM main GROUPBY region, dept
    
    >>> cursor.execute(query)
    >>> query_fields = [desc[0] for desc in cursor.description]
    >>> UserQuery = namedtuple('UserQuery', query_fields, rename=True)
    >>> pprint.pprint([UserQuery(*row) for row in cursor])
    [UserQuery(region='South', dept='Shipping', _2=185),
     UserQuery(region='North', dept='Accounting', _2=37),
     UserQuery(region='West', dept='Sales', _2=419)]
    

    (由 Raymond Hettinger 在 bpo-1818 中贡献。)

  • re.sub(), re.subn()re.split() 函数现在可接受一个 flags 形参。

    (由 Gregory Smith 贡献)

  • logging 模块现在为不使用 logging 但是调用了使用它的库代码的应用程序实现了一个简单的 logging.NullHandler 类。 设置一个空处理器将会屏蔽诸如 "找不到日志记录器 foo 的处理器" 这样的虚假警告:

    >>> h = logging.NullHandler()
    >>> logging.getLogger("foo").addHandler(h)
    

    (由 Vinay Sajip 在 bpo-4384 中贡献。)

  • 支持 -m 命令行开关的 runpy 模块现在也支持当提供包名称时通过查找并执行 __main__ 子模块来执行包。

    (由 Andi Vajda 在 bpo-4195 中贡献。)

  • pdb 模块现在可以访问并显示通过 zipimport (或其他符合规范的 PEP 302 加载器) 加载的源代码。

    (由 Alexander Belopolsky 在 bpo-4201 中贡献。)

  • functools.partial 对象现在可以被封存。

(由 Antoine Pitrou 和 Jesse Noller 提议,由 Jack Diederich 实现; bpo-5228。)

  • 为符号增加 pydoc 帮助主题以使得在交互环境下 help('@') 能符合预期的效果。

    (由 David Laban 在 bpo-4739 中贡献。)

  • unittest 模块现在支持跳过单个测试或测试类。 并且它还支持将一个测试标记为已预期会失败,即已经知道不可用,但不应在 TestResult 上被计为一次失败:

    class TestGizmo(unittest.TestCase):
    
        @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
        def test_gizmo_on_windows(self):
            ...
    
        @unittest.expectedFailure
        def test_gimzo_without_required_library(self):
            ...
    

    此外,还创建了一些针对异常的测试以便与使用 with 语句的上下文管理器一起工作:

    def test_division_by_zero(self):
        with self.assertRaises(ZeroDivisionError):
            x / 0
    

    此外,还增加了一些新的断言方法包括 assertSetEqual(), assertDictEqual(), assertDictContainsSubset(), assertListEqual(), assertTupleEqual(), assertSequenceEqual(), assertRaisesRegexp(), assertIsNone() 以及 assertIsNotNone()

    (由Benjamin Peterson 和 Antoine Pitrou 贡献。)

  • io 模块新增了三个常量来代表 seek() 方法 SEEK_SET, SEEK_CURSEEK_END

  • sys.version_info 元组现在是一个具名元组:

    >>> sys.version_info
    sys.version_info(major=3, minor=1, micro=0, releaselevel='alpha', serial=2)
    

    (由 Ross Light 在 bpo-4285 中贡献。)

  • nntplibimaplib 模块现在支持 IPv6。

    (由 Derek Morr 在 bpo-1655bpo-1664 中贡献。)

  • 当使用协议 2 或更低的版本时,pickle 模块已被调适以获得与 Python 2.x 更好的互操作性。 标准库的重组改变了对于许多对象的正式引用。 例如,Python 2 中的 __builtin__.set 在 Python 3 中称为 builtins.set。 这一改变使得在不同版本的 Python 之间共享数据的努力陷入混乱。 但是现在当选择协议 2 或更低的版本时,封存器将自动使用旧的 Python 2 名称进行加载和转储。 这样的重映射将默认启用但可以通过 fix_imports 选项来禁用:

    >>> s = {1, 2, 3}
    >>> pickle.dumps(s, protocol=0)
    b'c__builtin__\nset\np0\n((lp1\nL1L\naL2L\naL3L\natp2\nRp3\n.'
    >>> pickle.dumps(s, protocol=0, fix_imports=False)
    b'cbuiltins\nset\np0\n((lp1\nL1L\naL2L\naL3L\natp2\nRp3\n.'
    

    这项改变的一个不幸但无可避免的副作用是由 Python 3.1 所产生的协议 2 版本的 pickle 对象对于 Python 3.0 将不可读。 当在 Python 3.x 实现之间迁移数据时,应当使用最新的协议 3 版本的 pickle 协议,因为它不会试图与 Python 2.x 保持兼容。

    (由 Alexandre Vassalotti 和 Antoine Pitrou 在 bpo-6137 中贡献。)

  • 增加了一个新模块 importlib。 它提供了针对 import 语句及其对应物 __import__() 函数的完整、可移植的纯 Python 引用实现。 它代表了在记录和定义导入期间所发生的行动中实质性的一步。

    (由 Brett Cannon 贡献。)

性能优化

已增加的主要性能改善:

  • 新的 I/O 库(如 PEP 3116 所定义的)主要是用 Python 编写并很快被证明是 Python 3.0 中的一个问题瓶颈。 在 Python 3.1 中,I/O 库已完全用 C 重写并根据具体任务的不同有 2 到 20 倍的速度提升。 纯 Python 版本仍然可通过 _pyio 模块访问以用于实验性目的。

    (由 Amaury Forgeot d'Arc 和 Antoine Pitrou 贡献。)

  • 添加了一个启发式的工具以使仅包含不可追踪对象的元组和字典不会被垃圾回收器所追踪。 这可以减少收集数据的大小从而减少长时间运行的程序的垃圾收集开销,具体取决于它们对类型类型的使用方式。

    (由 Antoine Pitrou 在 bpo-4688 中贡献。)

  • 通过在受支持的编译器(主要有: gcc, SunPro, icc)上启用名为 --with-computed-gotos 的配置选项,字节码求值循环会使用新的分派机制进行编译,根据系统、编译器和基准测试工具的不同,该机制可获得至多 20% 的速度提升。

    (由 Antoine Pitrou 以及其他一些参与者在 bpo-4753 中贡献。)

  • UTF-8, UTF-16 和 LATIN-1 的解码速度现已提升了二至四倍。

    (由 Antoine Pitrou 和 Amaury Forgeot d'Arc 在 bpo-4868 中贡献。)

  • The json module now has a C extension to substantially improve its performance. In addition, the API was modified so that json works only with str, not with bytes. That change makes the module closely match the JSON specification which is defined in terms of Unicode.

    (由 Bob Ippolito 在 bpo-4136 中贡献。并由 Antoine Pitrou 和 Benjamin Peterson 转换为Py3.1)

  • 解封操作现在将固定已封存对象的属性名称。 这可以节省内存并让封存对象变得更小。

    (由 Jake McGuire 和 Antoine Pitrou 在 bpo-5084 中贡献。)

IDLE

  • IDLE 的格式菜单现在提供了一个从源文件中去除尾部空格的选项。

    (由 Roger D. Serwy 在 bpo-5150 中贡献。)

构建和 C API 的改变

针对 Python 构建过程和 C API 的改变包括:

  • 现在整数在内部是以 2**15 为基数或以 2**30 为基数来存储的,这个基数会在构建时被确定。 在之前版本中,它们总是以 2**15 为基数来存储。 在 64 位机器上使用 2**30 为基数可显著提升性能,但在 32 位机器上的基准测试结果则好坏参半。 因此,默认会在 64 位机器上使用 2**30 为基数而在 32 位机器上使用 2**15 为基数;在 Unix 上,有一个新的配置选项 --enable-big-digits 可被用于覆盖此默认值。

    除了性能提升之外这项改变对最终用户来说应当是不可见的,只有一个例外:出于测试和调试目的有一个新的提供相关内部格式信息的 sys.int_info,它给出了每个数位对应的比特位数和用于存储每个数位的以字节数表示的 C 类型大小:

    >>> import sys
    >>> sys.int_info
    sys.int_info(bits_per_digit=30, sizeof_digit=4)
    

    (由 Mark Dickinson在 bpo-4258 贡献)

  • PyLong_AsUnsignedLongLong() 函数现在将通过引发 OverflowError 而不是 TypeError 来处理负的 pylong

    (由 Mark Dickinson 和 Lisandro Dalcrin 在 bpo-5175 中贡献。)

  • 已弃用 PyNumber_Int() 。请改用 PyNumber_Long()

    (由 Mark Dickinson在 bpo-4910 贡献)

  • 增加了一个新的 PyOS_string_to_double() 函数来替代已弃用的函数 PyOS_ascii_strtod()PyOS_ascii_atof()

    (由 Mark Dickinson 在 bpo-5914 贡献)

  • 增加了 PyCapsule 作为 PyCObject API 的替代。 主要区别在于新类型拥有一个定义良好的接口用来传递类型安全信息并拥有一个较低复杂度的签名用来调用析构器。 旧类型的 API 存在问题且现在已被弃用。

    (由 Larry Hastings 在 bpo-5630 中贡献。)

移植到 Python 3.1

本节列出了先前描述的改变以及可能需要修改你的代码的其他问题修正:

  • 新的浮点字符串表示形式可能会破坏现有的 doctest。 例如:

    def e():
        '''Compute the base of natural logarithms.
    
        >>> e()
        2.7182818284590451
    
        '''
        return sum(1/math.factorial(x) for x in reversed(range(30)))
    
    doctest.testmod()
    
    **********************************************************************
    Failed example:
        e()
    Expected:
        2.7182818284590451
    Got:
        2.718281828459045
    **********************************************************************
    
  • 在 pickle 模块中用于协议 2 或更低版本的自动名称重映射会使得 Python 3.1 的 pickle 在 Python 3.0 中无法读取。 一种解决方案是使用协议 3。 另一种解决方案是将 fix_imports 选项设为 False。 请参阅上面的讨论来了解更多细节。