2to3 - 自動將 Python 2的程式碼轉成 Python 3

2to3 是一个 Python 程序,它可以读取 Python 2.x 的源代码并使用一系列的 修复器 来将其转换为合法的 Python 3.x 代码。 标准库已包含了丰富的修复器,这足以处理几乎所有代码。 不过 2to3 的支持库 lib2to3 是一个很灵活通用的库,所以还可以编写你自己的 2to3 修复器。

使用 2to3

2to3 通常会作为脚本和 Python 解释器一起安装,你可以在 Python 根目录的 Tools/scripts 文件夹下找到它。

2to3 的基本调用参数是一个需要转换的文件或目录列表。对于目录,会递归地寻找其中的 Python 源码。

這邊有簡單的 Python 2的原始檔案 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 参数则可以明确指定不使用的修复器。下面的例子会只使用 importshas_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 语句的代码运行修复器。 也可以使用 -eexec() 解读为函数。

-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

修复器

转换代码的每一个步骤都封装在修复器中。可以使用 2to3 -l 来列出可用的修复器。之前已经提到,每个修复器都可以独立地打开或是关闭。下面会对各个修复器做更详细的描述。

apply

移除对 apply() 的使用,举例来说,apply(function, *args, **kwargs) 会被转换成 function(*args, **kwargs)

asserts

将已弃用的 unittest 方法替换为正确的。

failUnlessEqual(a, b)

assertEqual(a, b)

assertEquals(a, b)

assertEqual(a, b)

failIfEqual(a, b)

assertNotEqual(a, b)

assertNotEquals(a, b)

assertNotEqual(a, b)

failUnless(a)

assertTrue(a)

assert_(a)

assertTrue(a)

failIf(a)

assertFalse(a)

failUnlessRaises(exc, cal)

assertRaises(exc, cal)

failUnlessAlmostEqual(a, b)

assertAlmostEqual(a, b)

assertAlmostEquals(a, b)

assertAlmostEqual(a, b)

failIfAlmostEqual(a, b)

assertNotAlmostEqual(a, b)

assertNotAlmostEquals(a, b)

assertNotAlmostEqual(a, b)

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

filter() 函数用 list 包装一层。

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 SomeClasstype(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

operator 模块中的许多方法调用转为其他的等效函数调用。如果有需要,会添加适当的 import 语句,比如 import collections.abc。有以下转换映射:

operator.isCallable(obj)

callable(obj)

operator.sequenceIncludes(obj)

operator.contains(obj)

operator.isSequenceType(obj)

isinstance(obj, collections.abc.Sequence)

operator.isMappingType(obj)

isinstance(obj, collections.abc.Mapping)

operator.isNumberType(obj)

isinstance(obj, numbers.Number)

operator.repeat(obj, n)

operator.mul(obj, n)

operator.irepeat(obj, n)

operator.imul(obj, n)

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

reload() 转换为 importlib.reload()

renames

sys.maxint 转换为 sys.maxsize

repr

将反引号 repr 表达式替换为 repr() 函数。

set_literal

set 构造函数替换为 set literals 写法。这个修复器是可选的。

standarderror

StandardError 重命名为 Exception

sys_exc

将弃用的 sys.exc_valuesys.exc_typesys.exc_traceback 替换为 sys.exc_info() 的用法。

throw

修复生成器的 throw() 方法的 API 变更。

tuple_params

移除隐式的元组参数解包。这个修复器会插入临时变量。

types

修复 type 模块中一些成员的移除引起的代码问题。

unicode

unicode 重命名为 str

urllib

urlliburllib2 重命名为 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 的话会禁用。

lib2to3 —— 2to3 支持库

源代码: Lib/lib2to3/


3.10 版後已棄用: Python 3.9 将切换到 PEG 解析器 (参见 PEP 617),Python 3.10 可能会包含 lib2to3 的 LL(1) 解析器所不能解析的新语法。 lib2to3 模块可能会在未来的 Python 版本中被移出标准库。 请考虑使用第三方替代例如 LibCSTparso

備註

lib2to3 API 并不稳定,并可能在未来大幅修改。