赞
踩
2011年2月9日
本文由Ian Ward提供
Python 3.0于2008年底发布,但到目前为止,只有相对较少的软件包更新到支持最新版本;大多数Python软件仍然只支持Python 2。 Python 3 引入了对 Unicode 和字符串处理、模块导入、整数表示和除法、打印语句以及其它一些不同之处的修改。 本文将介绍从 Python 2 移植到 Python 3 时最容易引起问题的一些变化,并介绍管理同时支持两个主要版本的单一代码库的一些策略。
Python 3 中的改动最初是被称为 "Python 3000 "的计划的一部分,这是一个关于语言改动的玩笑,这些改动只能在遥远的将来完成。 这些修改列出了 Python 语言中不一致的地方和不方便的设计,这些修改本来是很好修复的,但是因为修复它们意味着破坏所有现有的 Python 代码,所以只能等待。最终,Python 开发者们决定用一个真正稳定的版本来修复这些问题,并接受这样一个事实:大多数软件包和用户需要几年的时间来完成转换。
最大的变化是 Python 3 处理字符串的方式。 Python 2 有 8 位字符串和 Unicode 文本,而 Python 3 有 Unicode 文本和二进制数据。 在 Python 2 中,您可以自由地使用字符串和 Unicode 文本,使用任何一种类型的参数,并且在必要时自动转换。 这很好,直到您在字符串中得到一些 8 位数据,而某个函数 (在您的代码中的任何地方或您正在使用的库的深处) 需要 Unicode 文本。 然后,一切都崩溃了。 Python 2 试图将字符串解码为 7 位 ASCII 以获得 Unicode 文本,这就给开发者,或者更糟糕的是给最终用户留下了这些问题:
Traceback (most recent call last): ... UnicodeDecodeError: 'ascii' codec can't decode byte 0xf4 in position 3: \ ordinal not in range(128)
在 Python 3 中没有更多的自动转换,缺省几乎在所有地方都是 Unicode 文本。 Python 2 将'all\xf4'作为一个有 4 个字节的 8 位字符串,而 Python 3 将相同的字面作为 Unicode 文本,U+00F4 作为第四个字符。
在 Python 3 中以文本模式打开的文件 (缺省值,包括sys.stdin、sys.stdout 和sys.stderr),从read()返回 Unicode 文本,并期望传递 Unicode 文本给write()。 在二进制模式下打开的文件只操作二进制数据。 这个变化对 Linux 和其它类 Unix 操作系统中的 Python 用户的影响比对 Windows 和 Mac 用户的影响更大 - 在 Linux 上的 Python 2 中,以二进制模式打开的文件与以文本模式打开的文件几乎没有区别,而 Windows 和 Mac 用户已经习惯了 Python 在文本模式下至少要换行。
这意味着许多过去 "工作 "的代码 (这里的 "工作 "是指仅用于 ASCII 文本) 现在被破坏了。 但是,一旦这些代码被更新以正确处理哪些输入和输出是文本编码,哪些是二进制编码,那么那些母语或名称不符合ASCII码的人就可以轻松地使用这些代码了。 这是一个非常好的结果。
Python 3 用于二进制数据的bytes类型与 Python 2 的 8 位字符串有很大不同。 Python 2.6 和更高版本将bytes定义为与str类型相同,这有点奇怪,因为接口发生了很大的变化:
>>> bytes([2,3,4]) # Python 2 '[2, 3, 4]' >>> [x for x in 'abc'] ['a', 'b', 'c
在 Python 3 中,b''用于字节字面量:
>>> bytes([2,3,4]) # Python 3 b'\x02\x03\x04' >>> [x for x in b'abc'] [97, 98, 99]
Python 3 的字节类型可以被看作是一个不变的列表,其值在 0 到 255 之间。 这对于处理二进制数据时的位运算和其它数字运算很方便,但它与 Python 2 程序员期望的长度为 1 的字符串有很大不同。
整数也发生了变化。 长整数和普通整数之间没有区别,sys.maxint也消失了。 整数除法也发生了变化。 任何有 Python (或 C) 背景的人都会告诉你:
>>> 1/2 0 >>> 1.0/2 0.5
但不再是这样了。 幸运的是,Python 2.2 和更高版本有一个除法运算符(//)。 使用它,您可以确定结果是整数。
我要指出的最后一个大变化是比较。 在 Python 2 中,比较(<, <=,>=,>) 总是在所有对象之间定义。 当没有定义显式排序时,一种类型的所有对象将被任意地认为大于或小于另一种类型的所有对象。 因此,您可以对一个混合了多种类型的列表进行排序,然后将所有不同类型的对象归为一组。 但是大多数时候,您真的不想对不同类型的对象排序,这个特性只是隐藏了一些令人讨厌的 bug。
Python 3 现在会在比较不兼容类型的对象时引发TypeError,这是它应该做的。 注意所有类型的相等(==,!=) 仍然被定义。
模块导入发生了变化。 在 Python 2 中,导入时首先搜索包含源文件的目录 (称为 "相对导入"),然后依次尝试系统路径中的目录。 在 Python 3 中,相对导入必须是显式的:
from . import my_utils
在 Python 3 中,print语句变成了一个函数。 这段 Python 2 代码向sys.stderr打印一个字符串,在字符串末尾用空格代替换行:
import sys print >>sys.stderr, 'something bad happened:'、
变为
import sys print('something bad happened:', end=' ', file=sys.stderr)
这些只是一些最大的变化。 完整列表在这里。
幸运的是,Python 附带的2to3工具处理了大量的不兼容问题。 2to3使用 Python 2 源代码并执行一些自动替换,以准备代码在 Python 3 中运行。Print 语句变成了函数,Unicode 文本字面量去掉了"u"前缀,相对导入变得显式,等等。
不幸的是,其余的修改需要手工完成。
在2to3 的帮助下,维护一个可以在 Python 2 和 Python 3 中工作的单一代码库是合理的。 在我的库"Urwid "中,我的目标是 Python 2.4 及以上版本,这是我使用的兼容性代码的一部分。 当你真的需要写一些在 Python 2 和 Python 3 中使用不同路径的代码时,用"if PYTHON3:" 语句来明确一下是很好的:
import sys PYTHON3 = sys.version_info >= (3, 0) try:# 为 Python 2.4, 2.5 定义字节 bytes = bytes except NameError: bytes = str if PYTHON3: # 用于创建字节字符串 B = lambda x: x.encode('latin1') else: B = lambda x: x
字符串处理和字面字符串是最常见的需要更新的地方。 一些指导原则:
在源代码中的所有字面文本中使用Unicode字面(u'')。 这样您的意图就很明确,并且在 Python 3 中的行为也是一样的(2to3将把它们转换成普通的文本字符串)。
如果您支持的 Python 版本早于 2.6,则对所有字面的字节字符串使用字节字面(b'') 或上面的B()函数。 B()利用 Unicode 的前 256 个码位映射到 Latin-1 的事实,从 Unicode 文本创建二进制字符串。
只有在 Python 2 期望使用 8 位字符串,而 Python 3 期望使用 Unicode 文本的情况下,才使用普通字符串 ('') 。 这些情况包括属性名、标识符、docstrings 和__repr__返回值。
记录您的函数是接受字节还是 Unicode 文本,并防止传入错误的类型 (例如assert isinstance(var, unicode)),或者如果您必须接受两种类型,立即转换为 Unicode 文本。
在您的源代码中明确地将文本标记为文本,二进制标记为二进制,这可以作为文档,并且可以防止您编写的代码在 Python 3 下运行时失败。
跨 Python 版本处理二进制数据有几种方法。 如果用data[i:i+1] 替换所有单独的字节访问,例如 data[i],那么在 Python 2 和 Python 3 中都会得到长度为-1 的字节串。 然而,我更喜欢遵循 Python 3 的习惯,将字节字符串作为整数列表来处理,并使用一些更兼容的代码:
if PYTHON3: # 用于操作字节 ord2 = lambda x: x chr2 = lambda x: bytes([x]) else: ord2 = ord chr2 = chr
在 Python 2 或 Python 3 中,ord2返回一个字节的序号值 (在 Python 2 或 Python 3 中,它是一个无操作符),而chr2转换回一个字节字符串。 根据你处理二进制数据的方式,对整数序号值而不是长度为-1 的字节字符串进行操作可能会更快。
Python "doctests" 是出现在函数、类和模块文档文本中的测试代码片段。 测试代码类似于一个交互式的 Python 会话,包括代码的运行和输出。 对于简单的函数来说,这种测试通常就足够了,而且是很好的文档。 然而,Doctests对在同一个代码库中支持Python 2和Python 3提出了挑战。
2to3可以以与源代码其它部分相同的方式转换 doctest 代码,但它不会触及预期输出。 Python 2 会在长整型输出的末尾加上一个 "L",在 Unicode 字符串前面加上一个 "u",这些在 Python 3 中都不会出现,但是打印值的工作方式是一样的。 确保从 doctests 运行的其它代码一直输出相同的文本,如果不能,你可以在输出中使用ELLIPSIS标志和... 来掩盖微小的差异。
你还需要做一些简单的修改,包括
在所有需要底层划分的地方使用//(如上所述)。
从BaseException 派生异常类。
在 my_dict 中使用k而不是my _ dict.has_key(k)。
使用my_list.sort(key=custom_key_fn)代替 my_list.sort( custom _sort)。
使用distribute代替 Setuptools。
有两个额外的资源可能会有帮助:将 Python 代码移植到 3.0和编写向前兼容的 Python 代码。
Python 3 毫无疑问是比 Python 2 更好的语言。 许多刚开始学习 Python 3 的人,尤其是专有操作系统的用户,都会从 Python 3 开始。更多的 Python 2 用户对 Python 3 感兴趣,但他们使用的代码或库让他们望而却步。
通过在应用程序或库中添加 Python 3 支持,您可以帮助他们:
使刚开始使用 Python 3 的新用户可以使用它
鼓励现有用户采用它,因为这不会阻止他们以后切换到 Python 3
清理文本和二进制数据的模糊使用,并发现相关的 bug
作为一个小奖励,这些软件可以被列在Python Packaging Index 中支持Python 3 的软件包中,只需在首页点击一下即可。
许多流行的Python软件包还没有进行转换,但它肯定在每个人的雷达上。 我很幸运。 社区成员已经完成了将我的库移植到 Python 3 的大部分艰苦工作,我只需要更新我的测试,并想办法让这些改变也能在旧版本的 Python 上运行。
由于 Python 2 和 Python 3 之间的巨大差异,目前在 Python 社区中存在分歧。 但是通过一些努力,这种分歧是可以弥合的。 值得付出努力。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。