2to3 - 自動(dòng)將 Python 2 代碼轉(zhuǎn)為 Python 3 代碼?

2to3 是一個(gè) Python 程序,它可以讀取 Python 2.x 的源代碼并使用一系列的 修復(fù)器 來(lái)將其轉(zhuǎn)換為合法的 Python 3.x 代碼。 標(biāo)準(zhǔn)庫(kù)已包含了豐富的修復(fù)器,這足以處理幾乎所有代碼。 不過(guò) 2to3 的支持庫(kù) lib2to3 是一個(gè)很靈活通用的庫(kù),所以還可以編寫你自己的 2to3 修復(fù)器。

Deprecated since version 3.11, will be removed in version 3.13: The lib2to3 module was marked pending for deprecation in Python 3.9 (raising PendingDeprecationWarning on import) and fully deprecated in Python 3.11 (raising DeprecationWarning). The 2to3 tool is part of that. It will be removed in Python 3.13.

使用 2to3?

2to3 通常會(huì)作為腳本和 Python 解釋器一起安裝,你可以在 Python 根目錄的 Tools/scripts 文件夾下找到它。

2to3 的基本調(diào)用參數(shù)是一個(gè)需要轉(zhuǎn)換的文件或目錄列表。對(duì)于目錄,會(huì)遞歸地尋找其中的 Python 源碼。

這里有一個(gè) Python 2.x 的源碼文件,example.py

def greet(name):
    print "Hello, {0}!".format(name)
print "What's your name?"
name = raw_input()
greet(name)

它可以在命令行中使用 2to3 轉(zhuǎn)換成 Python 3.x 版本的代碼:

$ 2to3 example.py

這個(gè)命令會(huì)打印出和源文件的區(qū)別。通過(guò)傳入 -w 參數(shù),2to3 也可以把需要的修改寫回到原文件中(除非傳入了 -n 參數(shù),否則會(huì)為原始文件創(chuàng)建一個(gè)副本):

$ 2to3 -w example.py

在轉(zhuǎn)換完成后,example.py 看起來(lái)像是這樣:

def greet(name):
    print("Hello, {0}!".format(name))
print("What's your name?")
name = input()
greet(name)

注釋和縮進(jìn)都會(huì)在轉(zhuǎn)換過(guò)程中保持不變。

默認(rèn)情況下,2to3 會(huì)執(zhí)行 預(yù)定義修復(fù)器 的集合。使用 -l 參數(shù)可以列出所有可用的修復(fù)器。使用 -f 參數(shù)可以明確指定需要使用的修復(fù)器集合。而使用 -x 參數(shù)則可以明確指定不使用的修復(fù)器。下面的例子會(huì)只使用 importshas_key 修復(fù)器運(yùn)行:

$ 2to3 -f imports -f has_key example.py

這個(gè)命令會(huì)執(zhí)行除了 apply 之外的所有修復(fù)器:

$ 2to3 -x apply example.py

有一些修復(fù)器是需要 顯式指定 的,它們默認(rèn)不會(huì)執(zhí)行,必須在命令行中列出才會(huì)執(zhí)行。比如下面的例子,除了默認(rèn)的修復(fù)器以外,還會(huì)執(zhí)行 idioms 修復(fù)器:

$ 2to3 -f all -f idioms example.py

注意這里使用 all 來(lái)啟用所有默認(rèn)的修復(fù)器。

有些情況下 2to3 會(huì)找到源碼中有一些需要修改,但是無(wú)法自動(dòng)處理的代碼。在這種情況下,2to3 會(huì)在差異處下面打印一個(gè)警告信息。你應(yīng)該定位到相應(yīng)的代碼并對(duì)其進(jìn)行修改,以使其兼容 Python 3.x。

2to3 也可以重構(gòu) doctests。使用 -d 開(kāi)啟這個(gè)模式。需要注意*只有* doctests 會(huì)被重構(gòu)。這種模式下不需要文件是合法的 Python 代碼。舉例來(lái)說(shuō),reST 文檔中類似 doctests 的示例也可以使用這個(gè)選項(xiàng)進(jìn)行重構(gòu)。

-v 選項(xiàng)可以輸出更多轉(zhuǎn)換程序的詳細(xì)信息。

由于某些 print 語(yǔ)句可被解讀為函數(shù)調(diào)用或是語(yǔ)句,2to3 并不總能讀取包含 print 函數(shù)的文件。 當(dāng) 2to3 檢測(cè)到存在 from __future__ import print_function 編譯器指令時(shí),會(huì)修改其內(nèi)部語(yǔ)法將 print() 解讀為函數(shù)。 這一變動(dòng)也可以使用 -p 旗標(biāo)手動(dòng)開(kāi)啟。 使用 -p 來(lái)為已轉(zhuǎn)換過(guò) print 語(yǔ)句的代碼運(yùn)行修復(fù)器。 也可以使用 -eexec() 解讀為函數(shù)。

-o--output-dir 選項(xiàng)可以指定將轉(zhuǎn)換后的文件寫入其他目錄中。由于這種情況下不會(huì)覆寫原始文件,所以創(chuàng)建副本文件毫無(wú)意義,因此也需要使用 -n 選項(xiàng)來(lái)禁用創(chuàng)建副本。

3.2.3 新版功能: 增加了 -o 選項(xiàng)。

-W--write-unchanged-files 選項(xiàng)用來(lái)告訴 2to3 始終需要輸出文件,即使沒(méi)有任何改動(dòng)。這在使用 -o 參數(shù)時(shí)十分有用,這樣就可以將整個(gè) Python 源碼包完整地轉(zhuǎn)換到另一個(gè)目錄。這個(gè)選項(xiàng)隱含了 -w 選項(xiàng),否則等于沒(méi)有作用。

3.2.3 新版功能: 增加了 -W 選項(xiàng)。

--add-suffix 選項(xiàng)接受一個(gè)字符串,用來(lái)作為后綴附加在輸出文件名后面的后面。由于寫入的文件名與原始文件不同,所以沒(méi)有必要?jiǎng)?chuàng)建副本,因此 -n 選項(xiàng)也是必要的。舉個(gè)例子:

$ 2to3 -n -W --add-suffix=3 example.py

這樣會(huì)把轉(zhuǎn)換后的文件寫入 example.py3 文件。

3.2.3 新版功能: 增加了 --add-suffix 選項(xiàng)。

將整個(gè)項(xiàng)目從一個(gè)目錄轉(zhuǎn)換到另一個(gè)目錄可以用這樣的命令:

$ 2to3 --output-dir=python3-version/mycode -W -n python2-version/mycode

修復(fù)器?

轉(zhuǎn)換代碼的每一個(gè)步驟都封裝在修復(fù)器中。可以使用 2to3 -l 來(lái)列出可用的修復(fù)器。之前已經(jīng)提到,每個(gè)修復(fù)器都可以獨(dú)立地打開(kāi)或是關(guān)閉。下面會(huì)對(duì)各個(gè)修復(fù)器做更詳細(xì)的描述。

apply?

移除對(duì) apply() 的使用,舉例來(lái)說(shuō),apply(function, *args, **kwargs) 會(huì)被轉(zhuǎn)換成 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 轉(zhuǎn)換為 str。

buffer?

buffer 轉(zhuǎn)換為 memoryview。這個(gè)修復(fù)器是可選的,因?yàn)?memoryview API 和 buffer 很相似,但不完全一樣。

dict?

修復(fù)字典迭代方法。dict.iteritems() 會(huì)轉(zhuǎn)換成 dict.items(),dict.iterkeys() 會(huì)轉(zhuǎn)換成 dict.keys(),dict.itervalues() 會(huì)轉(zhuǎn)換成 dict.values()。類似的,dict.viewitems(),dict.viewkeys()dict.viewvalues() 會(huì)分別轉(zhuǎn)換成 dict.items(),dict.keys()dict.values()。另外也會(huì)將原有的 dict.items(),dict.keys()dict.values() 方法調(diào)用用 list 包裝一層。

except?

except X, T 轉(zhuǎn)換為 except X as T。

exec?

exec 語(yǔ)句轉(zhuǎn)換為 exec() 函數(shù)調(diào)用。

execfile?

移除 execfile() 的使用。execfile() 的實(shí)參會(huì)使用 open(),compile()exec() 包裝。

exitfunc?

將對(duì) sys.exitfunc 的賦值改為使用 atexit 模塊代替。

filter?

filter() 函數(shù)用 list 包裝一層。

funcattrs?

修復(fù)已經(jīng)重命名的函數(shù)屬性。比如 my_function.func_closure 會(huì)被轉(zhuǎn)換為 my_function.__closure__。

future?

移除 from __future__ import new_feature 語(yǔ)句。

getcwdu?

os.getcwdu() 重命名為 os.getcwd()

has_key?

dict.has_key(key) 轉(zhuǎn)換為 key in dict。

idioms?

這是一個(gè)可選的修復(fù)器,會(huì)進(jìn)行多種轉(zhuǎn)換,將 Python 代碼變成更加常見(jiàn)的寫法。類似 type(x) is SomeClasstype(x) == SomeClass 的類型對(duì)比會(huì)被轉(zhuǎn)換成 isinstance(x, SomeClass)。while 1 轉(zhuǎn)換成 while True。這個(gè)修復(fù)器還會(huì)在合適的地方使用 sorted() 函數(shù)。舉個(gè)例子,這樣的代碼塊:

L = list(some_iterable)
L.sort()

會(huì)被轉(zhuǎn)換為:

L = sorted(some_iterable)
import?

檢測(cè) sibling imports,并將其轉(zhuǎn)換成相對(duì) import。

imports?

處理標(biāo)準(zhǔn)庫(kù)模塊的重命名。

imports2?

處理標(biāo)準(zhǔn)庫(kù)中其他模塊的重命名。這個(gè)修復(fù)器由于一些技術(shù)上的限制,因此和 imports 拆分開(kāi)了。

input?

input(prompt) 轉(zhuǎn)換為 eval(input(prompt))。

intern?

intern() 轉(zhuǎn)換為 sys.intern()。

isinstance?

修復(fù) isinstance() 函數(shù)第二個(gè)實(shí)參中重復(fù)的類型。舉例來(lái)說(shuō),isinstance(x, (int, int)) 會(huì)轉(zhuǎn)換為 isinstance(x, int), isinstance(x, (int, float, int)) 會(huì)轉(zhuǎn)換為 isinstance(x, (int, float))

itertools_imports?

移除 itertools.ifilter()itertools.izip() 以及 itertools.imap() 的 import。對(duì) itertools.ifilterfalse() 的 import 也會(huì)替換成 itertools.filterfalse()

itertools?

修改 itertools.ifilter(),itertools.izip()itertools.imap() 的調(diào)用為對(duì)應(yīng)的內(nèi)建實(shí)現(xiàn)。itertools.ifilterfalse() 會(huì)替換成 itertools.filterfalse()。

long?

long 重命名為 int

map?

list 包裝 map()。同時(shí)也會(huì)將 map(None, x) 替換為 list(x)。使用 from future_builtins import map 禁用這個(gè)修復(fù)器。

metaclass?

將老的元類語(yǔ)法(類體中的 __metaclass__ = Meta)替換為新的(class X(metaclass=Meta))。

methodattrs?

修復(fù)老的方法屬性名。例如 meth.im_func 會(huì)被轉(zhuǎn)換為 meth.__func__

ne?

轉(zhuǎn)換老的不等語(yǔ)法,將 <> 轉(zhuǎn)為 !=

next?

將迭代器的 next() 方法調(diào)用轉(zhuǎn)為 next() 函數(shù)。也會(huì)將 next() 方法重命名為 __next__()。

nonzero?

Renames definitions of methods called __nonzero__() to __bool__().

numliterals?

將八進(jìn)制字面量轉(zhuǎn)為新的語(yǔ)法。

operator?

operator 模塊中的許多方法調(diào)用轉(zhuǎn)為其他的等效函數(shù)調(diào)用。如果有需要,會(huì)添加適當(dāng)?shù)?import 語(yǔ)句,比如 import collections.abc。有以下轉(zhuǎn)換映射:

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?

在列表生成式中增加必須的括號(hào)。例如將 [x for x in 1, 2] 轉(zhuǎn)換為 [x for x in (1, 2)]。

print?

print 語(yǔ)句轉(zhuǎn)換為 print() 函數(shù)。

raise?

raise E, V 轉(zhuǎn)換為 raise E(V),將 raise E, V, T 轉(zhuǎn)換為 raise E(V).with_traceback(T)。如果 E 是元組,這樣的轉(zhuǎn)換是不正確的,因?yàn)橛迷M代替異常的做法在 3.0 中已經(jīng)移除了。

raw_input?

raw_input() 轉(zhuǎn)換為 input()。

reduce?

reduce() 轉(zhuǎn)換為 functools.reduce()。

reload?

reload() 轉(zhuǎn)換為 importlib.reload()。

renames?

sys.maxint 轉(zhuǎn)換為 sys.maxsize

repr?

將反引號(hào) repr 表達(dá)式替換為 repr() 函數(shù)。

set_literal?

set 構(gòu)造函數(shù)替換為 set literals 寫法。這個(gè)修復(fù)器是可選的。

standarderror?

StandardError 重命名為 Exception

sys_exc?

將棄用的 sys.exc_value,sys.exc_type,sys.exc_traceback 替換為 sys.exc_info() 的用法。

throw?

修復(fù)生成器的 throw() 方法的 API 變更。

tuple_params?

移除隱式的元組參數(shù)解包。這個(gè)修復(fù)器會(huì)插入臨時(shí)變量。

types?

修復(fù) type 模塊中一些成員的移除引起的代碼問(wèn)題。

unicode?

unicode 重命名為 str

urllib?

urlliburllib2 重命名為 urllib 包。

ws_comma?

移除逗號(hào)分隔的元素之間多余的空白。這個(gè)修復(fù)器是可選的。

xrange?

xrange() 重命名為 range(),并用 list 包裝原有的 range()

xreadlines?

for x in file.xreadlines() 轉(zhuǎn)換為 for x in file。

zip?

list 包裝 zip()。如果使用了 from future_builtins import zip 的話會(huì)禁用。

lib2to3 —— 2to3 支持庫(kù)?

源代碼: Lib/lib2to3/


Deprecated since version 3.11, will be removed in version 3.13: Python 3.9 switched to a PEG parser (see PEP 617) while lib2to3 is using a less flexible LL(1) parser. Python 3.10 includes new language syntax that is not parsable by lib2to3's LL(1) parser (see PEP 634). The lib2to3 module was marked pending for deprecation in Python 3.9 (raising PendingDeprecationWarning on import) and fully deprecated in Python 3.11 (raising DeprecationWarning). It will be removed from the standard library in Python 3.13. Consider third-party alternatives such as LibCST or parso.

備注

lib2to3 API 并不穩(wěn)定,并可能在未來(lái)大幅修改。