您当前位置:首页 Dive Into Python 3

难度等级:♦♦♦♦♦

使用 2to3 将代码移植到 Python 3

生活是美好的。死亡是安宁的。是过渡很麻烦。
— 艾萨克·阿西莫夫(归因于)

 

深入

在 Python 2 和 Python 3 之间发生了很大的变化,几乎没有程序可以在两者中无修改地运行。但不要绝望!为了帮助进行这种过渡,Python 3 附带了一个名为 2to3 的实用程序脚本,该脚本将您的实际 Python 2 源代码作为输入,并尽可能自动转换为 Python 3。 案例研究:将 chardet 移植到 Python 3 描述了如何运行 2to3 脚本,然后展示了一些它无法自动修复的内容。本附录记录了它可以自动修复的内容。

print 语句

在 Python 2 中,print 是一个语句。您要打印的内容只需跟随 print 关键字即可。在 Python 3 中,print() 是一个函数。您要打印的内容,像任何其他函数一样传递给 print()

注释 Python 2 Python 3
print print()
print 1 print(1)
print 1, 2 print(1, 2)
print 1, 2, print(1, 2, end=' ')
print >>sys.stderr, 1, 2, 3 print(1, 2, 3, file=sys.stderr)
  1. 要打印空白行,请在不带任何参数的情况下调用 print()
  2. 要打印单个值,请使用一个参数调用 print()
  3. 要打印以空格分隔的两个值,请使用两个参数调用 print()
  4. 这个有点棘手。在 Python 2 中,如果用逗号结束 print 语句,它将以空格分隔打印值,然后打印尾随空格,然后停止而不打印回车符。(从技术上讲,这比这更复杂一点。Python 2 中的 print 语句使用了一个现已弃用的属性,称为 softspace。Python 2 不会打印空格,而是将 sys.stdout.softspace 设置为 1。空格字符直到在同一行上打印其他内容时才会真正打印出来。如果下一个 print 语句打印了一个回车符,sys.stdout.softspace 将被设置为 0,并且空格将永远不会被打印出来。除非您的应用程序对 print 生成的输出中尾随空格的存在或不存在敏感,否则您可能从未注意到这种差异。)在 Python 3 中,执行此操作的方法是将 end=' ' 作为关键字参数传递给 print() 函数。end 参数默认值为 '\n'(回车符),因此覆盖它将抑制打印其他参数后的回车符。
  5. 在 Python 2 中,您可以将输出重定向到管道(例如 sys.stderr)——使用 >>pipe_name 语法。在 Python 3 中,执行此操作的方法是将管道传递给 file 关键字参数。file 参数默认值为 sys.stdout(标准输出),因此覆盖它将输出到不同的管道。

Unicode 字符串字面量

Python 2 有两种字符串类型:Unicode 字符串和非 Unicode 字符串。Python 3 有一种字符串类型:Unicode 字符串

注释 Python 2 Python 3
u'PapayaWhip' 'PapayaWhip'
ur'PapayaWhip\foo' r'PapayaWhip\foo'
  1. Unicode 字符串字面量被简单地转换为字符串字面量,在 Python 3 中,它们始终是 Unicode。
  2. Unicode 原始字符串(Python 不会自动转义反斜杠)被转换为原始字符串。在 Python 3 中,原始字符串始终是 Unicode。

unicode() 全局函数

Python 2 有两个全局函数可以将对象强制转换为字符串:unicode() 将它们强制转换为 Unicode 字符串,而 str() 将它们强制转换为非 Unicode 字符串。Python 3 只有一个字符串类型,Unicode 字符串,因此 str() 函数是您唯一需要的。(unicode() 函数不再存在。)

注释 Python 2 Python 3
unicode(anything) str(anything)

long 数据类型

Python 2 针对非浮点数有单独的 intlong 类型。int 不能大于 sys.maxint,它因平台而异。Long 通过在数字末尾追加一个 L 来定义,并且它们可以比 ints 更长。在 Python 3 中,只有一种整数类型,称为 int,它在很大程度上类似于 Python 2 中的 long 类型。由于不再存在两种类型,因此不需要特殊的语法来区分它们。

进一步阅读:PEP 237:统一长整数和整数。

注释 Python 2 Python 3
x = 1000000000000L x = 1000000000000
x = 0xFFFFFFFFFFFFL x = 0xFFFFFFFFFFFF
long(x) int(x)
type(x) is long type(x) is int
isinstance(x, long) isinstance(x, int)
  1. 基数为 10 的长整数字面量变为基数为 10 的整数字面量。
  2. 基数为 16 的长整数字面量变为基数为 16 的整数字面量。
  3. 在 Python 3 中,旧的 long() 函数不再存在,因为 longs 不存在。要将变量强制转换为整数,请使用 int() 函数。
  4. 要检查变量是否为整数,请获取其类型并将其与 int 而不是 long 进行比较。
  5. 您还可以使用 isinstance() 函数来检查数据类型;同样,使用 int 而不是 long 来检查整数。

<> 比较

Python 2 支持 <> 作为 != 的同义词,即不等于比较运算符。Python 3 支持 != 运算符,但不支持 <>

注释 Python 2 Python 3
if x <> y if x != y
if x <> y <> z if x != y != z
  1. 一个简单的比较。
  2. 三个值之间更复杂的比较。

has_key() 字典方法

在 Python 2 中,字典有一个 has_key() 方法来测试字典是否具有某个键。在 Python 3 中,此方法不再存在。相反,您需要使用 in 运算符

注释 Python 2 Python 3
a_dictionary.has_key('PapayaWhip') 'PapayaWhip' in a_dictionary
a_dictionary.has_key(x) or a_dictionary.has_key(y) x in a_dictionary or y in a_dictionary
a_dictionary.has_key(x or y) (x or y) in a_dictionary
a_dictionary.has_key(x + y) (x + y) in a_dictionary
x + a_dictionary.has_key(y) x + (y in a_dictionary)
  1. 最简单的形式。
  2. in 运算符优先于 or 运算符,因此不需要在 x in a_dictionaryy in a_dictionary 周围加括号。
  3. 另一方面,您确实需要在 x or y 周围加括号,原因相同——in 优先于 or。(注意:此代码与上一行完全不同。Python 首先解释 x or y,这将导致 x(如果 x 在布尔上下文中为 true)或 y。然后它获取该单一值并检查它是否是 a_dictionary 中的键。)
  4. + 运算符优先于 in 运算符,因此此形式技术上不需要在 x + y 周围加括号,但 2to3 仍然包含它们。
  5. 此形式肯定需要在 y in a_dictionary 周围加括号,因为 + 运算符优先于 in 运算符。

返回列表的字典方法

在 Python 2 中,许多字典方法返回列表。最常用的方法是 keys()items()values()。在 Python 3 中,所有这些方法都返回动态 视图。在某些情况下,这不是问题。如果方法的返回值立即传递给另一个遍历整个序列的函数,那么实际类型是列表还是视图都没有区别。在其他情况下,这非常重要。如果您期望一个具有单独可寻址元素的完整列表,您的代码将出现故障,因为视图不支持索引。

注释 Python 2 Python 3
a_dictionary.keys() list(a_dictionary.keys())
a_dictionary.items() list(a_dictionary.items())
a_dictionary.iterkeys() iter(a_dictionary.keys())
[i for i in a_dictionary.iterkeys()] [i for i in a_dictionary.keys()]
min(a_dictionary.keys()) 无变化
  1. 2to3 以安全为重,使用 list() 函数将 keys() 的返回值转换为静态列表。这将始终有效,但效率低于使用视图。您应该检查转换后的代码,看看列表是否绝对必要,或者视图是否可以做到。
  2. 另一个视图到列表的转换,使用 items() 方法。2to3 将对 values() 方法执行相同的操作。
  3. Python 3 不再支持 iterkeys() 方法。使用 keys(),如果需要,使用 iter() 函数将视图转换为迭代器。
  4. 2to3 识别出 iterkeys() 方法在列表推导中使用的情况,并将其转换为 keys() 方法(不将其包装在对 iter() 的额外调用中)。这之所以有效是因为视图是可迭代的。
  5. 2to3 识别出 keys() 方法立即传递给一个函数,该函数遍历整个序列,因此不需要首先将返回值转换为列表。min() 函数将很乐意遍历视图。这适用于 min()max()sum()list()tuple()set()sorted()any()all()

已重命名或重新组织的模块

Python 标准库中的几个模块已重命名。一些相互关联的其他模块已组合或重新组织,使其关联更合理。

http

在 Python 3 中,几个相关的 HTTP 模块已合并到一个单独的包 http 中。

注释 Python 2 Python 3
import httplib import http.client
import Cookie import http.cookies
import cookielib import http.cookiejar
import BaseHTTPServer
import SimpleHTTPServer
import CGIHttpServer
import http.server
  1. http.client 模块实现了一个低级库,它可以请求 HTTP 资源并解释 HTTP 响应。
  2. http.cookies 模块为在 Set-Cookie: HTTP 标头中发送的浏览器 cookie 提供了一个 Pythonic 接口。
  3. http.cookiejar 模块操作流行的 Web 浏览器用来存储 cookie 的磁盘上的实际文件。
  4. http.server 模块提供了一个基本的 HTTP 服务器。

urllib

Python 2 有一堆重叠的模块来解析、编码和获取 URL。在 Python 3 中,这些模块都已重构并在一个单独的包 urllib 中组合在一起。

注释 Python 2 Python 3
import urllib import urllib.request, urllib.parse, urllib.error
import urllib2 import urllib.request, urllib.error
import urlparse import urllib.parse
import robotparser import urllib.robotparser
from urllib import FancyURLopener
from urllib import urlencode
from urllib.request import FancyURLopener
from urllib.parse import urlencode
from urllib2 import Request
from urllib2 import HTTPError
from urllib.request import Request
from urllib.error import HTTPError
  1. Python 2 中的旧 urllib 模块包含多种函数,包括用于获取数据的 urlopen(),以及用于将 URL 分解为其组成部分的 splittype()splithost()splituser()。这些函数在新的 urllib 包中以更合理的逻辑进行了重新组织。2to3 还会更改对这些函数的所有调用,使其使用新的命名方案。
  2. Python 2 中的旧 urllib2 模块已合并到 Python 3 中的 urllib 包中。你所有 urllib2 中的喜爱功能,比如 build_opener() 方法、Request 对象,以及 HTTPBasicAuthHandler 及其朋友,仍然可以使用。
  3. Python 3 中的 urllib.parse 模块包含 Python 2 中的旧 urlparse 模块的所有解析函数。
  4. urllib.robotparser 模块解析 robots.txt 文件。
  5. FancyURLopener 类处理 HTTP 重定向和其他状态码,仍然在新 urllib.request 模块中可用。urlencode() 函数已移至 urllib.parse
  6. Request 对象仍然在 urllib.request 中可用,但 HTTPError 等常量已移至 urllib.error

我是否提到 2to3 也会重写你的函数调用?例如,如果你的 Python 2 代码导入 urllib 模块并调用 urllib.urlopen() 来获取数据,2to3 将修复导入语句和函数调用。

注释 Python 2 Python 3
import urllib
print urllib.urlopen('http://diveintopython3.org/').read()
import urllib.request, urllib.parse, urllib.error
print(urllib.request.urlopen('http://diveintopython3.org/').read())

dbm

所有不同的 DBM 克隆现在都放在一个包 dbm 中。如果你需要特定变体,例如 GNU DBM,你可以导入 dbm 包中的相应模块。

注释 Python 2 Python 3
import dbm import dbm.ndbm
import gdbm import dbm.gnu
import dbhash import dbm.bsd
import dumbdbm import dbm.dumb
import anydbm
import whichdb
import dbm

xmlrpc

XML-RPC 是一种轻量级方法,用于通过 HTTP 执行远程 RPC 调用。 XML-RPC 客户端库和多个 XML-RPC 服务器实现现在合并到一个包 xmlrpc 中。

注释 Python 2 Python 3
import xmlrpclib import xmlrpc.client
import DocXMLRPCServer
import SimpleXMLRPCServer
import xmlrpc.server

其他模块

注释 Python 2 Python 3
try:
    import cStringIO as StringIO
except ImportError:
    import StringIO
import io
try:
    import cPickle as pickle
except ImportError:
    import pickle
import pickle
import __builtin__ import builtins
import copy_reg import copyreg
import Queue import queue
import SocketServer import socketserver
import ConfigParser import configparser
import repr import reprlib
import commands import subprocess
  1. 在 Python 2 中,一个常见的习惯用法是尝试导入 cStringIO as StringIO,如果失败,则导入 StringIO。在 Python 3 中不要这样做;io 模块为你完成。它会找到可用的最快实现并自动使用它。
  2. 一个类似的习惯用法用于导入最快的 pickle 实现。在 Python 3 中不要这样做;pickle 模块为你完成。
  3. builtins 模块包含贯穿整个 Python 语言使用的全局函数、类和常量。在 builtins 模块中重新定义一个函数将重新定义所有地方的全局函数。这正是它听起来那样强大和可怕。
  4. copyreg 模块为在 C 中定义的自定义类型添加 pickle 支持。
  5. queue 模块实现了一个多生产者、多消费者队列。
  6. socketserver 模块提供了用于实现不同类型的套接字服务器的通用基类。
  7. configparser 模块解析 INI 风格的配置文件。
  8. reprlib 模块重新实现内置的 repr() 函数,并对表示可以有多长进行额外控制,然后再对其进行截断。
  9. subprocess 模块允许你生成进程,连接到它们的管道,并获取它们的返回值。

包中的相对导入

一个包是一组相关的模块,它们作为一个整体起作用。在 Python 2 中,当包中的模块需要相互引用时,使用 import foofrom foo import Bar。Python 2 解释器首先在当前包中搜索 foo.py,然后继续搜索 Python 搜索路径(sys.path)中的其他目录。Python 3 的工作方式略有不同。它不会搜索当前包,而是直接进入 Python 搜索路径。如果你希望包中的一个模块导入同一个包中的另一个模块,你需要显式提供两个模块之间的相对路径。

假设你有这个包,在同一个目录中有多个文件

chardet/
|
+--__init__.py
|
+--constants.py
|
+--mbcharsetprober.py
|
+--universaldetector.py

现在假设 universaldetector.py 需要导入整个 constants.py 文件和来自 mbcharsetprober.py 的一个类。你该如何做到这一点?

注释 Python 2 Python 3
import constants from . import constants
from mbcharsetprober import MultiByteCharSetProber from .mbcharsetprober import MultiByteCharsetProber
  1. 当你需要从包中的其他地方导入整个模块时,使用新的 from . import 语法。句点实际上是从该文件(universaldetector.py)到你想要导入的文件(constants.py)的相对路径。在本例中,它们在同一个目录中,因此只有一个句点。你也可以从父目录(from .. import anothermodule)或子目录中导入。
  2. 要直接将来自另一个模块的特定类或函数导入模块的命名空间,请在目标模块前面加上一个相对路径,去掉尾部斜杠。在本例中,mbcharsetprober.pyuniversaldetector.py 位于同一个目录中,因此路径是一个句点。你也可以从父目录(from ..anothermodule import AnotherClass)或子目录中导入。

next() 迭代器方法

在 Python 2 中,迭代器有一个 next() 方法,它返回序列中的下一个项目。这在 Python 3 中仍然适用,但现在也有一个 全局 next() 函数,它以迭代器作为参数。

注释 Python 2 Python 3
anIterator.next() next(anIterator)
a_function_that_returns_an_iterator().next() next(a_function_that_returns_an_iterator())
class A:
    def next(self):
        pass
class A:
    def __next__(self):
        pass
class A:
    def next(self, x, y):
        pass
无变化
next = 42
for an_iterator in a_sequence_of_iterators:
    an_iterator.next()
next = 42
for an_iterator in a_sequence_of_iterators:
    an_iterator.__next__()
  1. 在最简单的情况下,不再调用迭代器的 next() 方法,而是将迭代器本身传递给全局 next() 函数。
  2. 如果你有一个返回迭代器的函数,调用该函数并将结果传递给 next() 函数。(2to3 脚本足够聪明,可以正确地转换这一点。)
  3. 如果你定义了自己的类并打算将其用作迭代器,请定义 __next__() 特殊方法。
  4. 如果你定义了自己的类并且恰好有一个名为 next() 的方法,它接受一个或多个参数,2to3 不会修改它。此类不能用作迭代器,因为它的 next() 方法接受参数。
  5. 这个有点棘手。如果你有一个名为 next 的局部变量,那么它优先于新的全局 next() 函数。在这种情况下,你需要调用迭代器的特殊 __next__() 方法来获取序列中的下一个项目。(或者,你也可以重构代码,使局部变量不叫 next,但 2to3 不会自动为你做到这一点。)

filter() 全局函数

在 Python 2 中,filter() 函数返回一个列表,它是通过一个函数过滤序列的结果,该函数对序列中的每个项目返回 TrueFalse。在 Python 3 中,filter() 函数返回一个迭代器,而不是列表。

注释 Python 2 Python 3
filter(a_function, a_sequence) list(filter(a_function, a_sequence))
list(filter(a_function, a_sequence)) 无变化
filter(None, a_sequence) [i for i in a_sequence if i]
for i in filter(None, a_sequence) 无变化
[i for i in filter(a_function, a_sequence)] 无变化
  1. 在最基本的情况下,2to3 会用对 list() 的调用来包装对 filter() 的调用,它只是遍历其参数并返回一个真正的列表。
  2. 但是,如果对 filter() 的调用已经用 list() 包装,2to3 不会做任何事情,因为 filter() 返回一个迭代器这一事实无关紧要。
  3. 对于 filter(None, ...) 的特殊语法,2to3 会将调用转换为语义等效的列表推导式。
  4. for 循环等上下文中,无论如何都要遍历整个序列,因此不需要更改。
  5. 同样,不需要更改,因为列表推导式会遍历整个序列,如果 filter() 返回一个迭代器,它可以像返回列表一样做到这一点。

map() 全局函数

filter() 非常相似,map() 函数现在返回一个迭代器。(在 Python 2 中,它返回一个列表。)

注释 Python 2 Python 3
map(a_function, 'PapayaWhip') list(map(a_function, 'PapayaWhip'))
map(None, 'PapayaWhip') list('PapayaWhip')
map(lambda x: x+1, range(42)) [x+1 for x in range(42)]
for i in map(a_function, a_sequence) 无变化
[i for i in map(a_function, a_sequence)] 无变化
  1. filter() 一样,在最基本的情况下,2to3 会用对 list() 的调用来包装对 map() 的调用。
  2. 对于 map(None, ...) 的特殊语法,身份函数,2to3 会将其转换为等效的 list() 调用。
  3. 如果 map() 的第一个参数是 lambda 函数,2to3 会将其转换为等效的列表推导式。
  4. for 循环等上下文中,无论如何都要遍历整个序列,因此不需要更改。
  5. 同样,不需要更改,因为列表推导式会遍历整个序列,如果 map() 返回一个迭代器,它可以像返回列表一样做到这一点。

reduce() 全局函数

在 Python 3 中,reduce() 函数已从全局命名空间中移除,并放置在 functools 模块中。

注释 Python 2 Python 3
reduce(a, b, c)
from functools import reduce
reduce(a, b, c)

apply() 全局函数

Python 2 曾经有一个名为 apply() 的全局函数,它接受一个函数 f 和一个列表 [a, b, c] 并返回 f(a, b, c)。你可以通过直接调用该函数并向它传递以星号开头的参数列表来实现相同的效果。在 Python 3 中,apply() 函数不再存在;你必须使用星号表示法。

注释 Python 2 Python 3
apply(a_function, a_list_of_args) a_function(*a_list_of_args)
apply(a_function, a_list_of_args, a_dictionary_of_named_args) a_function(*a_list_of_args, **a_dictionary_of_named_args)
apply(a_function, a_list_of_args + z) a_function(*a_list_of_args + z)
apply(aModule.a_function, a_list_of_args) aModule.a_function(*a_list_of_args)
  1. 以最简单的形式,你可以通过在列表前面加上星号 (*) 来调用带有参数列表(实际的列表,例如 [a, b, c])的函数。这与 Python 2 中的旧 apply() 函数完全等效。
  2. 在 Python 2 中,apply() 函数实际上可以接受三个参数:一个函数、一个参数列表和一个命名参数字典。在 Python 3 中,你可以通过在参数列表前面加上星号 (*) 并在命名参数字典前面加上两个星号 (**) 来实现相同的效果。
  3. + 运算符在这里用于列表连接,它的优先级高于 * 运算符,因此不需要在 a_list_of_args + z 周围添加额外的括号。
  4. 2to3 脚本足够聪明,可以转换复杂的 apply() 调用,包括调用导入模块中的函数。

intern() 全局函数

在 Python 2 中,您可以对字符串调用 intern() 函数以将其内部化,以提高性能。在 Python 3 中,intern() 函数已移至 sys 模块。

注释 Python 2 Python 3
intern(aString) sys.intern(aString)

exec 语句

就像 print 语句 在 Python 3 中变成了函数一样,exec 语句也是如此。exec() 函数接受一个包含任意 Python 代码的字符串,并将其作为另一个语句或表达式执行。exec() 类似于 eval(),但功能更强大且更危险。eval() 函数只能评估单个表达式,但 exec() 可以执行多个语句、导入、函数声明——本质上是字符串中的整个 Python 程序。

注释 Python 2 Python 3
exec codeString exec(codeString)
exec codeString in a_global_namespace exec(codeString, a_global_namespace)
exec codeString in a_global_namespace, a_local_namespace exec(codeString, a_global_namespace, a_local_namespace)
  1. 在最简单的形式中,2to3 脚本只需将代码作为字符串括在括号中,因为 exec() 现在是函数而不是语句。
  2. 旧的 exec 语句可以接受一个命名空间,一个全局变量的私有环境,代码作为字符串将在其中执行。Python 3 也可以做到这一点;只需将命名空间作为 exec() 函数的第二个参数传递即可。
  3. 更妙的是,旧的 exec 语句还可以接受一个局部命名空间(比如函数中定义的变量)。在 Python 3 中,exec() 函数也可以做到这一点。

execfile 语句

像旧的 exec 语句 一样,旧的 execfile 语句会将字符串作为 Python 代码执行。exec 接受字符串,execfile 接受文件名。在 Python 3 中,execfile 语句已被删除。如果您确实需要获取一个 Python 代码文件并执行它(但您不愿意简单地导入它),您可以通过打开文件、读取其内容、调用全局 compile() 函数强制 Python 解释器编译代码来实现相同的功能,然后调用新的 exec() 函数。

注释 Python 2 Python 3
execfile('a_filename') exec(compile(open('a_filename').read(), 'a_filename', 'exec'))

repr 字面量(反引号)

在 Python 2 中,有一种特殊的语法,将任何对象包装在 反引号 中(比如 `x`)以获取对象的表示形式。在 Python 3 中,此功能仍然存在,但您不再可以使用反引号来获取它。而是使用全局 repr() 函数。

注释 Python 2 Python 3
`x` repr(x)
`'PapayaWhip' + `2`` repr('PapayaWhip' + repr(2))
  1. 请记住,x 可以是任何东西——类、函数、模块、基本数据类型、&c。repr() 函数对所有东西都有效。
  2. 在 Python 2 中,反引号可以嵌套,导致这种令人困惑(但有效)的表达式。2to3 工具足够智能,可以将其转换为对 repr() 的嵌套调用。

try...except 语句

捕获 异常 的语法在 Python 2 和 Python 3 之间略有不同。

注释 Python 2 Python 3
try:
    import mymodule
except ImportError, e
    pass
try:
    import mymodule
except ImportError as e:
    pass
try:
    import mymodule
except (RuntimeError, ImportError), e
    pass
try:
    import mymodule
except (RuntimeError, ImportError) as e:
    pass
try:
    import mymodule
except ImportError:
    pass
无变化
try:
    import mymodule
except:
    pass
无变化
  1. Python 3 使用一个新的关键字 as 来代替异常类型后的逗号。
  2. as 关键字也可以用于一次捕获多种类型的异常。
  3. 如果您捕获异常但实际上并不关心访问 异常 对象本身,则 Python 2 和 Python 3 之间的语法相同。
  4. 类似地,如果您使用回退来捕获所有异常,则语法相同。

在导入模块(或大多数其他情况下),您永远不应该使用回退来捕获所有异常。这样做会导致捕获诸如 KeyboardInterrupt 之类的东西(如果用户按下 Ctrl-C 来中断程序),并且会使调试错误变得更加困难。

raise 语句

在 Python 2 和 Python 3 之间,引发您自己的异常 的语法略有不同。

注释 Python 2 Python 3
raise MyException 保持不变
raise MyException, 'error message' raise MyException('error message')
raise MyException, 'error message', a_traceback raise MyException('error message').with_traceback(a_traceback)
raise 'error message' 不支持
  1. 在最简单的形式中,不带自定义错误消息引发异常,语法保持不变。
  2. 当您想要引发带有自定义错误消息的异常时,更改就会显而易见。Python 2 使用逗号将异常类和消息分开;Python 3 将错误消息作为参数传递。
  3. Python 2 支持更复杂的语法来引发带有自定义回溯(堆栈跟踪)的异常。您也可以在 Python 3 中执行此操作,但语法截然不同。
  4. 在 Python 2 中,您可以引发没有异常类,只有错误消息的异常。在 Python 3 中,这不再可能。2to3 会警告您,它无法自动修复此问题。

生成器上的 throw 方法

在 Python 2 中,生成器有一个 throw() 方法。调用 a_generator.throw() 会在生成器暂停的位置引发异常,然后返回生成器函数产生的下一个值。在 Python 3 中,此功能仍然可用,但语法略有不同。

注释 Python 2 Python 3
a_generator.throw(MyException) 无变化
a_generator.throw(MyException, 'error message') a_generator.throw(MyException('error message'))
a_generator.throw('error message') 不支持
  1. 在最简单的形式中,生成器会引发没有自定义错误消息的异常。在这种情况下,Python 2 和 Python 3 之间的语法没有改变。
  2. 如果生成器引发带有自定义错误消息的异常,则需要在创建异常时将错误字符串传递给异常。
  3. Python 2 还支持只用自定义错误消息引发异常。Python 3 不支持此操作,2to3 脚本将显示一个警告,告诉您需要手动修复此代码。

xrange() 全局函数

在 Python 2 中,有两种方法可以获取数字范围:range(),它返回一个列表,以及 xrange(),它返回一个迭代器。在 Python 3 中,range() 返回一个迭代器,而 xrange() 不存在。

注释 Python 2 Python 3
xrange(10) range(10)
a_list = range(10) a_list = list(range(10))
[i for i in xrange(10)] [i for i in range(10)]
for i in range(10) 无变化
sum(range(10)) 无变化
  1. 在最简单的情况下,2to3 脚本只会将 xrange() 转换为 range()
  2. 如果您的 Python 2 代码使用 range()2to3 脚本不知道您是否需要列表,或者迭代器是否可以。它会谨慎行事,并通过调用 list() 函数将返回值强制转换为列表。
  3. 如果 xrange() 函数位于列表推导中,2to3 脚本足够聪明,用对 range() 函数进行 list() 调用。列表推导将与 range() 函数返回的迭代器一起正常工作。
  4. 类似地,for 循环将与迭代器一起正常工作,因此这里无需更改任何内容。
  5. sum() 函数也将与迭代器一起工作,因此 2to3 在这里也不进行任何更改。就像 返回视图而不是列表的字典方法 一样,这适用于 min()max()sum()list()tuple()set()sorted()any()all()

raw_input()input() 全局函数

Python 2 有两个全局函数,用于在命令行上询问用户输入。第一个名为 input(),它期望用户输入一个 Python 表达式(并返回结果)。第二个名为 raw_input(),它只返回用户输入的任何内容。这对初学者来说非常混乱,并且被广泛认为是该语言中的“疣”。Python 3 通过将 raw_input() 重命名为 input() 来剔除这种“疣”,因此它按每个人天真地期望的方式工作。

注释 Python 2 Python 3
raw_input() input()
raw_input('prompt') input('prompt')
input() eval(input())
  1. 在最简单的形式中,raw_input() 变成 input()
  2. 在 Python 2 中,raw_input() 函数可以接受提示作为参数。这在 Python 3 中得到了保留。
  3. 如果您实际上需要询问用户要评估的 Python 表达式,请使用 input() 函数并将结果传递给 eval()

func_* 函数属性

在 Python 2 中,函数内的代码可以访问有关函数本身的特殊属性。在 Python 3 中,这些特殊函数属性已被重命名,以与其他属性保持一致。

注释 Python 2 Python 3
a_function.func_name a_function.__name__
a_function.func_doc a_function.__doc__
a_function.func_defaults a_function.__defaults__
a_function.func_dict a_function.__dict__
a_function.func_closure a_function.__closure__
a_function.func_globals a_function.__globals__
a_function.func_code a_function.__code__
  1. __name__ 属性(以前是 func_name)包含函数的名称。
  2. __doc__ 属性(以前是 func_doc)包含您在函数源代码中定义的文档字符串
  3. __defaults__ 属性(以前是 func_defaults)是一个元组,包含具有默认值的那些参数的默认参数值。
  4. __dict__ 属性(以前是 func_dict)是支持任意函数属性的命名空间。
  5. __closure__ 属性(以前是 func_closure)是一个单元格元组,包含函数的自由变量的绑定。
  6. __globals__ 属性(以前是 func_globals)是对定义函数的模块的全局命名空间的引用。
  7. __code__ 属性(以前是 func_code)是一个代码对象,它表示已编译的函数主体。

xreadlines() I/O 方法

在 Python 2 中,文件对象有一个 xreadlines() 方法,它返回一个迭代器,该迭代器会逐行读取文件。这在 for 循环中非常有用,在其他地方也一样。事实上,它非常有用,Python 2 的后续版本将此功能添加到文件对象本身。

在 Python 3 中,xreadlines() 方法不再存在。2to3 可以修复简单情况,但某些边缘情况需要手动干预。

注释 Python 2 Python 3
for line in a_file.xreadlines() for line in a_file
for line in a_file.xreadlines(5) 没有变化(已损坏)
  1. 如果您以前在没有参数的情况下调用 xreadlines()2to3 会将其转换为仅文件对象。在 Python 3 中,这将完成相同的事情:逐行读取文件并执行 for 循环的主体。
  2. 如果您过去使用带参数的 xreadlines()(一次读取的行数),2to3 不会修复它,并且您的代码将因 AttributeError: '_io.TextIOWrapper' object has no attribute 'xreadlines' 错误而失败。您可以手动将 xreadlines() 更改为 readlines() 以使其在 Python 3 中工作。(readlines() 方法现在返回一个迭代器,因此它与 Python 2 中的 xreadlines() 一样高效。)

采用元组而不是多个参数的 lambda 函数

在 Python 2 中,您可以定义匿名 lambda 函数,这些函数通过将函数定义为采用包含特定数量项的元组来接收多个参数。实际上,Python 2 将“解包”元组为命名参数,然后您可以在 lambda 函数中引用这些参数(按名称)。在 Python 3 中,您仍然可以将元组传递给 lambda 函数,但 Python 解释器不会将元组解包为命名参数。相反,您需要按其位置索引引用每个参数。

注释 Python 2 Python 3
lambda (x,): x + f(x) lambda x1: x1[0] + f(x1[0])
lambda (x, y): x + f(y) lambda x_y: x_y[0] + f(x_y[1])
lambda (x, (y, z)): x + y + z lambda x_y_z: x_y_z[0] + x_y_z[1][0] + x_y_z[1][1]
lambda x, y, z: x + y + z 保持不变
  1. 如果您定义了一个采用一个项目元组的 lambda 函数,在 Python 3 中它将变成一个引用 x1[0]lambdax1 的名称由 2to3 脚本根据原始元组中的命名参数自动生成。
  2. 具有两个项目元组 (x, y)lambda 函数被转换为 x_y,其中位置参数为 x_y[0]x_y[1]
  3. 2to3 脚本甚至可以处理具有嵌套命名参数元组的 lambda 函数。生成的 Python 3 代码有点难以阅读,但它与 Python 2 中的旧代码工作方式相同。
  4. 您可以定义接受多个参数的 lambda 函数。如果参数周围没有括号,Python 2 只将其视为具有多个参数的 lambda 函数;在 lambda 函数中,您只需按名称引用参数,就像任何其他函数一样。此语法在 Python 3 中仍然有效。

特殊方法属性

在 Python 2 中,类方法可以引用定义它们的类对象以及方法对象本身。im_self 是类实例对象;im_func 是函数对象;im_classim_self 的类。在 Python 3 中,这些特殊方法属性已重命名以遵循其他属性的命名约定。

注释 Python 2 Python 3
aClassInstance.aClassMethod.im_func aClassInstance.aClassMethod.__func__
aClassInstance.aClassMethod.im_self aClassInstance.aClassMethod.__self__
aClassInstance.aClassMethod.im_class aClassInstance.aClassMethod.__self__.__class__

__nonzero__ 特殊方法

在 Python 2 中,您可以构建自己的类,这些类可以在布尔上下文中使用。例如,您可以实例化该类,然后在 if 语句中使用该实例。要做到这一点,您定义了一个特殊的 __nonzero__() 方法,该方法返回 TrueFalse,并且每当该实例在布尔上下文中使用时都会调用它。在 Python 3 中,您仍然可以这样做,但方法的名称已更改为 __bool__()

注释 Python 2 Python 3
class A:
    def __nonzero__(self):
        pass
class A:
    def __bool__(self):
        pass
class A:
    def __nonzero__(self, x, y):
        pass
无变化
  1. Python 3 在布尔上下文中评估实例时调用 __bool__() 方法,而不是 __nonzero__()
  2. 但是,如果您有一个带参数的 __nonzero__() 方法,2to3 工具将假设您将其用于其他目的,并且不会进行任何更改。

八进制字面量

在 Python 2 和 Python 3 之间,定义 8 进制 (octal) 数字的语法略有不同。

注释 Python 2 Python 3
x = 0755 x = 0o755

sys.maxint

由于 longint 类型 集成sys.maxint 常量不再准确。因为该值可能仍然在确定特定于平台的功能方面有用,所以它已被保留但更名为 sys.maxsize

注释 Python 2 Python 3
from sys import maxint from sys import maxsize
a_function(sys.maxint) a_function(sys.maxsize)
  1. maxint 变为 maxsize
  2. 任何使用 sys.maxint 的地方都将变为 sys.maxsize

callable() 全局函数

在 Python 2 中,您可以使用全局 callable() 函数检查一个对象是否可调用(例如函数)。在 Python 3 中,此全局函数已被删除。要检查对象是否可调用,请检查 __call__() 特殊方法是否存在。

注释 Python 2 Python 3
callable(anything) hasattr(anything, '__call__')

zip() 全局函数

在 Python 2 中,全局 zip() 函数接受任意数量的序列并返回元组列表。第一个元组包含来自每个序列的第一个项目;第二个元组包含来自每个序列的第二个项目;等等。在 Python 3 中,zip() 返回一个迭代器而不是列表。

注释 Python 2 Python 3
zip(a, b, c) list(zip(a, b, c))
d.join(zip(a, b, c)) 无变化
  1. 在最简单的情况下,您可以通过将返回值包装在对 list() 的调用中来获得 zip() 函数的旧行为,这将遍历 zip() 返回的迭代器并返回结果的真实列表。
  2. 在已经遍历序列所有项目(例如对 join() 方法的此调用)的上下文中,zip() 返回的迭代器将正常工作。2to3 脚本足够智能,可以检测到这些情况并且不更改您的代码。

StandardError 异常

在 Python 2 中,StandardError 是除 StopIterationGeneratorExitKeyboardInterruptSystemExit 之外所有内置异常的基类。在 Python 3 中,StandardError 已被删除;请改用 Exception

注释 Python 2 Python 3
x = StandardError() x = Exception()
x = StandardError(a, b, c) x = Exception(a, b, c)

types 模块常量

types 模块包含各种常量,以帮助您确定对象的类型。在 Python 2 中,它包含所有原始类型的常量,如 dictint。在 Python 3 中,这些常量已被删除;只需使用原始类型名称即可。

注释 Python 2 Python 3
types.UnicodeType str
types.StringType bytes
types.DictType dict
types.IntType int
types.LongType int
types.ListType list
types.NoneType type(None)
types.BooleanType bool
types.BufferType memoryview
types.ClassType type
types.ComplexType complex
types.EllipsisType type(Ellipsis)
types.FloatType float
types.ObjectType object
types.NotImplementedType type(NotImplemented)
types.SliceType slice
types.TupleType tuple
types.TypeType type
types.XRangeType range

types.StringType 被映射到 bytes 而不是 str,因为 Python 2 中的“字符串”(不是 Unicode 字符串,只是普通字符串)实际上只是一个特定字符编码中的字节序列。

isinstance() 全局函数

isinstance() 函数检查对象是否为特定类或类型的实例。在 Python 2 中,您可以传递类型元组,如果对象是其中任何一种类型,则 isinstance() 将返回 True。在 Python 3 中,您仍然可以这样做,但传递两次相同的类型是已弃用的。

注释 Python 2 Python 3
isinstance(x, (int, float, int)) isinstance(x, (int, float))

basestring 数据类型

Python 2 具有两种字符串类型:Unicode 和非 Unicode。但也存在另一种类型,basestring。它是一个抽象类型,是 strunicode 类型的超类。它不能直接调用或实例化,但您可以将其传递给全局 isinstance() 函数以检查对象是 Unicode 字符串还是非 Unicode 字符串。在 Python 3 中,只有一种字符串类型,因此 basestring 没有存在的理由。

注释 Python 2 Python 3
isinstance(x, basestring) isinstance(x, str)

itertools 模块

Python 2.3 引入了 itertools 模块,该模块定义了全局 zip()map()filter() 函数的变体,这些变体返回迭代器而不是列表。在 Python 3 中,这些全局函数返回迭代器,因此 itertools 模块中的这些函数已被删除。(itertools 模块中仍然有很多有用的函数 ,只是没有这些。)

注释 Python 2 Python 3
itertools.izip(a, b) zip(a, b)
itertools.imap(a, b) map(a, b)
itertools.ifilter(a, b) filter(a, b)
from itertools import imap, izip, foo from itertools import foo
  1. 请勿使用 itertools.izip(),而直接使用全局 zip() 函数。
  2. 请勿使用 itertools.imap(),而直接使用 map()
  3. itertools.ifilter() 变为 filter()
  4. itertools 模块仍然存在于 Python 3 中,只是没有迁移到全局命名空间的函数。2to3 脚本足够智能,可以删除不再存在的特定导入,同时保持其他导入不变。

sys.exc_typesys.exc_valuesys.exc_traceback

Python 2 在 sys 模块中具有三个变量,您可以在处理异常时访问它们:sys.exc_typesys.exc_valuesys.exc_traceback。(实际上,这些变量可以追溯到 Python 1。)从 Python 1.5 开始,这些变量已被弃用,取而代之的是 sys.exc_info(),它是一个返回包含这三个值的元组的函数。在 Python 3 中,这些单个变量最终消失了;您必须使用 sys.exc_info() 函数。

注释 Python 2 Python 3
sys.exc_type sys.exc_info()[0]
sys.exc_value sys.exc_info()[1]
sys.exc_traceback sys.exc_info()[2]

元组上的列表推导

在 Python 2 中,如果您想编写一个迭代元组的列表推导,则不需要在元组值周围添加括号。在 Python 3 中,需要显式括号。

注释 Python 2 Python 3
[i for i in 1, 2] [i for i in (1, 2)]

os.getcwdu() 函数

Python 2 具有一个名为 os.getcwd() 的函数,它返回当前工作目录作为(非 Unicode)字符串。由于现代文件系统可以处理任何字符编码的目录名称,因此 Python 2.3 引入了 os.getcwdu()os.getcwdu() 函数返回当前工作目录作为 Unicode 字符串。在 Python 3 中,只有一种字符串类型(Unicode),因此 os.getcwd() 是您唯一需要的。

注释 Python 2 Python 3
os.getcwdu() os.getcwd()

元类

在 Python 2 中,您可以通过在类声明中定义 metaclass 参数,或者通过定义特殊的类级 __metaclass__ 属性来创建元类。在 Python 3 中,类级属性已被删除。

注释 Python 2 Python 3
class C(metaclass=PapayaMeta):
    pass
保持不变
class Whip:
    __metaclass__ = PapayaMeta
class Whip(metaclass=PapayaMeta):
    pass
class C(Whipper, Beater):
    __metaclass__ = PapayaMeta
class C(Whipper, Beater, metaclass=PapayaMeta):
    pass
  1. 在类声明中声明元类在 Python 2 中有效,并且在 Python 3 中仍然以相同的方式有效。
  2. 在类属性中声明元类在 Python 2 中有效,但在 Python 3 中无效。
  3. 2to3 脚本足够智能,可以构建有效的类声明,即使该类继承自一个或多个基类。

风格问题

这里列出的其余“修复”并非真正的修复。也就是说,它们更改的内容是风格问题,而不是实质问题。它们在 Python 3 中的工作原理与在 Python 2 中一样,但 Python 开发人员有强烈的兴趣使 Python 代码尽可能统一。为此,有一个官方的 Python 风格指南,它详细地概述了您几乎肯定不关心的各种挑剔细节。鉴于 2to3 为将 Python 代码从一种形式转换为另一种形式提供了如此出色的基础设施,作者便承担了添加一些可选功能以提高 Python 程序的可读性的责任。

set() 字面量(显式)

在 Python 2 中,在代码中定义字面量集合的唯一方法是调用 set(a_sequence)。这在 Python 3 中仍然有效,但更清晰的方法是使用新的集合字面量表示法:花括号。这对除空集以外的所有内容都有效,因为字典也使用花括号,所以 {} 是一个空字典,而不是一个空集合

2to3 脚本默认情况下不会修复 set() 字面量。要启用此修复,请在调用 2to3 时在命令行上指定 -f set_literal

注释 之前 之后
set([1, 2, 3]) {1, 2, 3}
set((1, 2, 3)) {1, 2, 3}
set([i for i in a_sequence]) {i for i in a_sequence}

buffer() 全局函数(显式)

用 C 实现的 Python 对象可以导出“缓冲区接口”,这允许其他 Python 代码直接读写一块内存。(这与听起来一样强大且可怕。)在 Python 3 中,buffer() 已重命名为 memoryview()。(它比这更复杂,但您几乎可以肯定可以忽略差异。)

2to3 脚本默认情况下不会修复 buffer() 函数。要启用此修复,请在调用 2to3 时在命令行上指定 -f buffer

注释 之前 之后
x = buffer(y) x = memoryview(y)

逗号周围的空格(显式)

尽管 Python 在缩进和缩进方面非常严格,但实际上在其他方面对空格非常宽松。在列表、元组、集合和字典中,逗号前后可以出现空格,不会产生任何不良影响。但是,Python 风格指南规定逗号前面应为零个空格,后面应为一个空格。尽管这纯粹是美学问题(代码在 Python 2 和 Python 3 中都可以正常工作),但 2to3 脚本可以选择为您修复此问题。

2to3 脚本默认情况下不会修复逗号周围的空格。要启用此修复,请在调用 2to3 时在命令行上指定 -f wscomma

注释 之前 之后
a ,b a, b
{a :b} {a: b}

常见习语(显式)

Python 社区中积累了许多常见的习语。有些,比如 while 1: 循环,可以追溯到 Python 1。(Python 直到版本 2.3 才拥有真正的布尔类型,因此开发人员使用 1 和 0 代替。)现代 Python 程序员应该训练自己的大脑使用这些习语的现代版本。

2to3 脚本默认情况下不会修复常见习语。要启用此修复,请在调用 2to3 时在命令行上指定 -f idioms

注释 之前 之后
while 1:
    do_stuff()
while True:
    do_stuff()
type(x) == T isinstance(x, T)
type(x) is T isinstance(x, T)
a_list = list(a_sequence)
a_list.sort()
do_stuff(a_list)
a_list = sorted(a_sequence)
do_stuff(a_list)

© 2001–11 马克·皮尔格里姆