26.7. 2to3 - 自动将 Python 2 代码转为 Python 3 代码
2to3 是一个 Python 程序,它可以用来读取 Python 2.x 版本的代码,并使用一系列的 修复器 来将其转换为合法的 Python 3.x 代码。标准库中已经包含了丰富的修复器,这足以处理绝大多数代码。不过 2to3 的支持库 lib2to3 是一个很灵活通用的库,所以你也可以为 2to3 编写你自己的修复器。lib2to3 也可以用在那些需要自动处理 Python 代码的应用中。
26.7.1. 使用 2to3
2to3 通常会作为脚本和 Python 解释器一起安装,你可以在 Python 根目录的 Tools/scripts
文件夹下找到它。
2to3 的基本调用参数是一个需要转换的文件或目录列表。对于目录,会递归地寻找其中的 Python 源码。
这里有一个 Python 2.x 的源码文件,example.py
:
def greet(name):
print "Hello, {0}!".format(name)
print "What's your name?"
name = raw_input()
greet(name)
它可以在命令行中使用 2to3 转换成 Python 3.x 版本的代码:
$ 2to3 example.py
这个命令会打印出和源文件的区别。通过传入 -w
参数,2to3 也可以把需要的修改写回到原文件中(除非传入了 -n
参数,否则会为原始文件创建一个副本):
$ 2to3 -w example.py
在转换完成后,example.py
看起来像是这样:
def greet(name):
print("Hello, {0}!".format(name))
print("What's your name?")
name = input()
greet(name)
注释和缩进都会在转换过程中保持不变。
默认情况下,2to3 会执行 预定义修复器 的集合。使用 -l
参数可以列出所有可用的修复器。使用 -f
参数可以明确指定需要使用的修复器集合。而使用 -x
参数则可以明确指定不使用的修复器。下面的例子会只使用 imports
和 has_key
修复器运行:
$ 2to3 -f imports -f has_key example.py
这个命令会执行除了 apply
之外的所有修复器:
$ 2to3 -x apply example.py
有一些修复器是需要 显式指定 的,它们默认不会执行,必须在命令行中列出才会执行。比如下面的例子,除了默认的修复器以外,还会执行 idioms
修复器:
$ 2to3 -f all -f idioms example.py
注意这里使用 all
来启用所有默认的修复器。
有些情况下 2to3 会找到源码中有一些需要修改,但是无法自动处理的代码。在这种情况下,2to3 会在差异处下面打印一个警告信息。你应该定位到相应的代码并对其进行修改,以使其兼容 Python 3.x。
2to3 也可以重构 doctests。使用 -d
开启这个模式。需要注意只有 doctests 会被重构。这种模式下不需要文件是合法的 Python 代码。举例来说,reST 文档中类似 doctests 的示例也可以使用这个选项进行重构。
-v
选项可以输出更多转换程序的详细信息。
由于某些 print 语句可被解读为函数调用或是语句,2to3 并不是总能读取包含 print 函数的文件。当 2to3 检测到存在 from __future__ import print_function
编译器指令时,会修改其内部语法将 print() 解读为函数。这一变动也可以使用 -p
选项手动开启。使用 -p
来为已经转换过 print 语句的代码运行修复器。
-o
或 --output-dir
选项可以指定将转换后的文件写入其他目录中。由于这种情况下不会覆写原始文件,所以创建副本文件毫无意义,因此也需要使用 -n
选项来禁用创建副本。
3.2.3 新版功能: 增加了 -o
选项。
-W
或 --write-unchanged-files
选项用来告诉 2to3 始终需要输出文件,即使没有任何改动。这在使用 -o
参数时十分有用,这样就可以将整个 Python 源码包完整地转换到另一个目录。这个选项隐含了 -w
选项,否则等于没有作用。
3.2.3 新版功能: 增加了 -W
选项。
--add-suffix
选项接受一个字符串,用来作为后缀附加在输出文件名后面的后面。由于写入的文件名与原始文件不同,所以没有必要创建副本,因此 -n
选项也是必要的。举个例子:
$ 2to3 -n -W --add-suffix=3 example.py
这样会把转换后的文件写入 example.py3
文件。
3.2.3 新版功能: 增加了 --add-suffix
选项。
将整个项目从一个目录转换到另一个目录可以用这样的命令:
$ 2to3 --output-dir=python3-version/mycode -W -n python2-version/mycode
26.7.2. 修复器
转换代码的每一个步骤都封装在修复器中。可以使用 2to3 -l
来列出可用的修复器。之前已经提到,每个修复器都可以独立地打开或是关闭。下面会对各个修复器做更详细的描述。
apply
移除对 apply()
的使用,举例来说,apply(function, *args, **kwargs)
会被转换成 function(*args, **kwargs)
。
asserts
将已弃用的 unittest 方法替换为正确的。
从 | 到 |
---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
basestring
将 basestring
转换为 str。
buffer
将 buffer
转换为 memoryview。这个修复器是可选的,因为 memoryview API 和 buffer
很相似,但不完全一样。
dict
修复字典迭代方法。dict.iteritems()
会转换成 dict.items(),dict.iterkeys()
会转换成 dict.keys(),dict.itervalues()
会转换成 dict.values()。类似的,dict.viewitems()
,dict.viewkeys()
和 dict.viewvalues()
会分别转换成 dict.items(),dict.keys() 和 dict.values()。另外也会将原有的 dict.items(),dict.keys() 和 dict.values() 方法调用用 list 包装一层。
except
将 except X, T
转换为 except X as T
。
exec
将 exec
语句转换为 exec() 函数调用。
execfile
移除 execfile()
的使用。execfile()
的实参会使用 open(),compile() 和 exec() 包装。
exitfunc
将对 sys.exitfunc
的赋值改为使用 atexit 模块代替。
filter
funcattrs
修复已经重命名的函数属性。比如 my_function.func_closure
会被转换为 my_function.__closure__
。
future
移除 from __future__ import new_feature
语句。
getcwdu
将 os.getcwdu()
重命名为 os.getcwd()。
has_key
将 dict.has_key(key)
转换为 key in dict
。
idioms
这是一个可选的修复器,会进行多种转换,将 Python 代码变成更加常见的写法。类似 type(x) is SomeClass
和 type(x) == SomeClass
的类型对比会被转换成 isinstance(x, SomeClass)
。while 1
转换成 while True
。这个修复器还会在合适的地方使用 sorted() 函数。举个例子,这样的代码块:
L = list(some_iterable)
L.sort()
会被转换为:
L = sorted(some_iterable)
import
检测 sibling imports,并将其转换成相对 import。
imports
处理标准库模块的重命名。
imports2
处理标准库中其他模块的重命名。这个修复器由于一些技术上的限制,因此和 imports 拆分开了。
input
将 input(prompt)
转换为 eval(input(prompt))
。
intern
将 intern()
转换为 sys.intern()。
isinstance
修复 isinstance() 函数第二个实参中重复的类型。举例来说,isinstance(x, (int, int))
会转换为 isinstance(x, int)
, isinstance(x, (int, float, int))
会转换为 isinstance(x, (int, float))
。
itertools_imports
移除 itertools.ifilter()
,itertools.izip()
以及 itertools.imap()
的 import。对 itertools.ifilterfalse()
的 import 也会替换成 itertools.filterfalse()。
itertools
修改 itertools.ifilter()
,itertools.izip()
和 itertools.imap()
的调用为对应的内建实现。itertools.ifilterfalse()
会替换成 itertools.filterfalse()。
long
将 long
重命名为 int。
map
用 list 包装 map()。同时也会将 map(None, x)
替换为 list(x)
。使用 from future_builtins import map
禁用这个修复器。
metaclass
将老的元类语法(类体中的 __metaclass__ = Meta
)替换为新的(class X(metaclass=Meta)
)。
methodattrs
修复老的方法属性名。例如 meth.im_func
会被转换为 meth.__func__
。
ne
转换老的不等语法,将 <>
转为 !=
。
next
将迭代器的 next()
方法调用转为 next() 函数。也会将 next() 方法重命名为 __next__()。
nonzero
将 __nonzero__()
转换为 __bool__()。
numliterals
将八进制字面量转为新的语法。
operator
Converts calls to various functions in the operator module to other, but equivalent, function calls. When needed, the appropriate import
statements are added, e.g. import collections
. The following mapping are made:
从 | 到 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
paren
在列表生成式中增加必须的括号。例如将 [x for x in 1, 2]
转换为 [x for x in (1, 2)]
。
print
将 print
语句转换为 print() 函数。
raise
将 raise E, V
转换为 raise E(V)
,将 raise E, V, T
转换为 raise E(V).with_traceback(T)
。如果 E
是元组,这样的转换是不正确的,因为用元组代替异常的做法在 3.0 中已经移除了。
raw_input
将 raw_input()
转换为 input()。
reduce
将 reduce()
转换为 functools.reduce()。
reload
Converts reload()
to imp.reload().
renames
将 sys.maxint
转换为 sys.maxsize。
repr
将反引号 repr 表达式替换为 repr() 函数。
set_literal
将 set 构造函数替换为 set literals 写法。这个修复器是可选的。
standarderror
将 StandardError
重命名为 Exception。
sys_exc
将弃用的 sys.exc_value
,sys.exc_type
,sys.exc_traceback
替换为 sys.exc_info() 的用法。
throw
修复生成器的 throw()
方法的 API 变更。
tuple_params
移除隐式的元组参数解包。这个修复器会插入临时变量。
types
修复 type 模块中一些成员的移除引起的代码问题。
unicode
将 unicode
重命名为 str。
urllib
将 urllib 和 urllib2
重命名为 urllib 包。
ws_comma
移除逗号分隔的元素之间多余的空白。这个修复器是可选的。
xrange
将 xrange()
重命名为 range(),并用 list 包装原有的 range()。
xreadlines
将 for x in file.xreadlines()
转换为 for x in file
。
zip
用 list 包装 zip()。如果使用了 from future_builtins import zip
的话会禁用。
26.7.3. lib2to3 —— 2to3 支持库
源代码: Lib/lib2to3/
注解
lib2to3 API 并不稳定,并可能在未来大幅修改。