從 2008年 Python 3.0釋出,帶來了許多新特性,由於新版本採用了破壞向後相容性(Backward compatibility)的方式,不可避免地需要面對程式碼要改寫的問題。本篇就一步步地帶讀者實作python 2, 3 的移植。
Python 版本概觀可參考 IThome 上的這篇 Python 2.x或3.x?
實際上的 python 2, 3 移植可參考官網 Porting Python 2 Code to Python 3
可以先用 python 內建的 2to3 跑過一次,在 cmd 中下:>> 2to3 -w [資料夾]
-w 為複寫的意思,不然他只會顯示要修改的地方不會複寫,
更多用法可以參考 https://docs.python.org/2/library/2to3.html 。
編碼原理可參考官方網站 或 https://openhome.cc/Gossip/Encoding/Python.html 也有說明。
跑完 2to3 後,大部分功能應該都沒問題了,可能遇到的會是編碼的問題。 Python 2 使用的是 普通字串(8-bit 字串) 和 unicode 字串的分別; 而 Python 3 使用的是 普通字串(unicode 編碼) 和 byte 物件的分別。
因此許多 testcase 會不通過的原因:
self.assertTrue(f('parameter') == 'answer')
# 在 python 2 中,原本預期 f 會回傳 answer 字串
self.assertTrue(f('parameter') == b'answer')
# 在 python 3 中,就有可能出現 fail,
# 原因是 byte != str,此時只要加上前綴 'b' 指明是 byte 就好了
此外,為了推廣 python 3, 基金會讓 python 2.6 及 2.7 也可以使用 python 3 的前綴 ‘b’; 讓 python 3 以後 也可以使擁有 python 2 的前綴 ‘u’, 都加上去就不用擔心相容的問題。
在專案中時常出現 types.MethodType 的錯誤,MethodType 為一可以綁函式在新方法上的函數;起因是 python 2 中此方法要接收 ,python 3 只要接收 就好。
if six.PY2:
newCallable = types.MethodType(
newCallable, callable.im_self, callable.im_class)
else:
newCallable = types.MethodType(newCallable, callable.__self__)
而在解決此問題當中,也發現了 python 3 中把 unbound method 移除了,因此:
infodict = getInfo(method.__func__)
# Python 2: 要特別找出 function
infodict = getInfo(method)
# Python 3: 只要不是 bound 的,就是 funtion
可參考 https://stackoverflow.com/questions/114214/class-method-differences-in-python-bound-unbound-and-static ,內有 method 和 function 說明。
此外, six 套件也有提供許多取得 function 或 method 的方法,
像是 get_unbound_function、get_method_function…
詳見:http://pythonhosted.org/six/#object-model-compatibility
補充: 在 py2中有分為 unbound 與 bound method
class A(object):
def f(self):
pass
a = A()
a.f #<bound method A.f of <__main__.A object at 0x0000000003AA9BE0>>
A.f #<unbound method A.f>
A.f(1) # TypeError: unbound method f() must be called with A instance as first argument
A.f(a) # ok
但是在 py3 中被拿掉了,只剩下 bound method
class A(object):
def f(self):
pass
a = A()
a.f # <bound method A.f of <__main__.A object at 0x0000018F2E61E710>>
A.f # <function A.f at 0x0000018F2E580D08>
A.f(1) # ok
所以 MethodType 可以改成以下
# 原本 MethodType 的功用是可以在已存在的 class 中加上 method,
#分別放入 types.MethodType(function, unbound 的話是 None, class)
emModels.Newsletter.getSubscriberUrl = types.MethodType(
getSubscriberUrl, None, emModels.Newsletter)
#在 py3 可以直接改成下面
emModels.Newsletter.getSubscriberUrl = getSubscriberUrl
Packages 的不相容,會是整個專案移植下來最大的問題。 測試的方法為先安裝專案看看,看哪個套件會出問題,就先從 REQUIREMENTS.txt 中拿掉,再一個一個處理。 處理時,可大概分成三種情況:
版本問題: 新的版本有相容 Python 3,建議可以先到 Pypi 上或官網上看官 方說明有無支援,有的話就只要更動版本號就好了。
easy_install 問題:
此種問題可能是解壓縮時,產生的編碼問題,或是其他;
因此當安裝失敗時,都可以先試試用 pip 裝看看,假使成功, 就可以考慮在 setup.py 中用 pip 裝。
編譯問題:
當 python 有用到 C extention 的 packages 時,有時候他會需 要用到 Visual C++ 的 Compiler 去編譯,不然會出現
>> "cl.exe not found"… 等等的錯誤。
版本對應:(Visual C++ → CPython) 14.0 → 3.5, 3.6 10.0 → 3.3, 3.4 9.0 → 2.6, 2.7, 3.0, 3.1, 3.2
因此必須先裝好 Visual Studio C++ Build Tools
更多資訊可以參考:
https://wiki.python.org/moin/WindowsCompilers
然而,也不是安裝完編譯器就能每次都順利安裝,有可能遇到 其他問題,可參考這篇, https://knowledge.nuwainfo.com/kb/bckiTlpabrrzHNnj
除了自己找解法外,也可以到 公司的 github 上看有沒有編好的 wheel 檔,直接安裝,跳過編譯的步驟。
套件名稱改變:
這部分只要改成正確的名稱就好了,上網應該都能輕易找到;
值得一提的是, six 中有滿多常見的改名,出問題也可以先過
去瞄瞄看,http://pythonhosted.org/six/#module-six.moves ,
程式碼中也可以用 six.moves 達到 2, 3 相容的目的。
這裡舉幾個比較分散的小個案,給讀者當作參考。
Django 中 smart_unicode 方法改為 smart_text
這部分說明了因應 python 2, 3 的改變,有些模組的方法也會跟
著改變,這時候就要去嗑文擋了。
open 在 python 2 不吃 encoding 參數,可以用
from io import open 來複寫舊的 open。
metaclass
在 python 3 是寫在參數列中(def f(metaclass=...)),而
在 python 2 是寫在 class 內(__metaclass __ = ...),
six 中有提供 @six.add_metaclass 可以使用。