雖然Python 3的官方文檔努陰人們寫同時支持Python 2和Python 3的代碼,可是在一此狀況這是合適的。尤爲是你不能放棄支持Python 2.5及更早的版本時,由於Python 2.6引進了至關多的向前兼容。html
使相同的代碼在更新的版本一樣運行是有可能的,不過你開始進入Python 3文檔中提到的「扭曲」的代碼風格。我會使用一些技巧來作這些而且我在本章末尾提到的six模塊會提供不少幫助。它甚至在一些比較大的項目中使用,可是我一般不推薦在在型項目中使用。對於小項目或者大項目的一部分,例如引導腳本,不使用2to3來支持老的Python版本是最好的解決的方式。python
Python 2.7對Python 3的兼容有一些小改進,但若是你想要在Python 2和Python 3下執行相同的代碼,彷佛你必需要在未來的一段時間內支持Python 2.6。ide
不少你須要的修改將會被2to3處理,因此開始轉換你的代碼你事實上須要首先在你的代碼上運行2to3而且確保你的代碼能夠在Python 3下運行。它一般很容易,或者至少在Python 3代碼中引入Python 2兼容比在Python 2中引入Python 3代碼更不單調。函數
一旦你有一個在Python 3下的項目,償試在Python 2.6下運行他們。在這一步你能夠執行出語法錯誤。他們應該只是來由print語句的變化。一旦你修復了他們,你能夠修復剩下的錯誤,而後你最終能夠在更早的Python版本作相同的事,若是你想一樣支持他們。測試
語法錯誤的較多狀況是print從語句變成了一個函數。這個簡單的狀況不是問題,你能夠簡單地在須要打印的文字周圍加上括號。後面的例子可以在全部的Python版本正確地打印出相同的內容:編碼
>>> print("This works in all versions of Python!") This works in all versions of Python!
然而,若是你使用了一些更高級的print特性However, 要麼你會以一個語法錯誤結束,要麼打印出的不是你但願的。Python 2的後續命令在Python 3中變成了一個參數,因此若是你使用後續命令來取消打印以後的新行,在Python 3能夠這樣作print('Textto print', end=' '),而這在Python 2是一個語法錯誤。url
在Python 2.6下有一個__future__導入可讓print成爲一個函數。因此爲了不一些語法錯誤及其餘一些不一樣,在你用到print()的文件應該要以from __future__ import print_function開始。這個__future__導入只能在Python 2.6及以後的版本工做,所爲爲了Python 2.5和更早的版本的你有兩個選擇。你要麼把更復雜的print轉換成更簡單的,要麼使用在Python 2和Python 3下都能工做的特殊print。spa
寫一個你本身的print函數聽起來比實際上更復雜。有一個技巧是使用sys.stdout.write()而且依照在函數中傳入的參數作格式化。然而更加容易使用的是我將要本章結尾提到的six模塊中的print_()函數。操作系統
若是你在你的異常處理裏須要訪問異常自己,你須要使用一個異常變量。在Python 2 這個語法是:.net
>>> try: ... a = 1/0 ... except ZeroDivisionError, e: ... print e.args[0] integer division or modulo by zero
然而,當你要捕捉不僅一個異常時這個語法是使人困惑的。你須要在異常值班表周圍加上括號:
>>> try: ... a = 1/"Dinsdale" ... except (ZeroDivisionError, TypeError): ... print "You can't divide by zero or by strings!" You can't divide by zero or by strings!
若是你忘記了括號,那麼只有ZeroDivisionError會被捕捉而且引發的異常儲存在一個名爲TypeError的變量裏。那不是你指望有。所以,在Python 3,這個語法改爲使用as來取代逗號,以免錯誤。當你捕捉多個異常時若是沒有括號它也會給你一個語法錯誤:
>>> try: ... a = 1/'0' ... except (ZeroDivisionError, TypeError) as e: ... print(e.args[0]) unsupported operand type(s) for /: 'int' and 'str'
前面的語法也能夠在Python 2.6中工做,Python 2.6能夠同時使用舊的逗號語法和舊的as關鍵字語法。若是你須要支持比Python 2.6更低的Python版本而且你須要訪問產生的異常,你能夠經過exc_info()函數在全部版本作到:
>>> import sys >>> try: ... a = 1/'0' ... except (ZeroDivisionError, TypeError): ... e = sys.exc_info()[1] ... print(e.args[0]) unsupported operand type(s) for /: 'int' and 'str'
另外一個不一樣之處在於Exception是不長的迭代器。在Python 2給異常的參數是能夠迭代遍歷異常或者下標異常。
>>> try: ... f = 1/0 ... except ZeroDivisionError, e: ... for m in e: ... print m ... print e[0] integer division or modulo by zero integer division or modulo by zero
在Python 3你須要使用異常的args屬性來替代:
>>> try: ... f = "1" + 1 ... except TypeError as e: ... for m in e.args: ... print(m) ... print(e.args[0]) Can't convert 'int' object to str implicitly Can't convert 'int' object to str implicitly
一個message屬性已經被添加到了Python 2.5的內置異常。然而它在Python 2.6已經被棄用並在Python 3移除。當使用-3標誌時Python 2.6和Python 2.7爲這個警報你。
大改變之一是標準庫的整改,結果你在這步獲得的常見錯誤是導入錯誤。繞過它是很容易的。你能夠簡單地償試從Python 3的位置醜話而且在失敗的時候從Python 2的位置導入。對於被重命名的模塊,你只要把他們導入成新的名字。
>>> try: ... import configparser ... except ImportError: ... import ConfigParser as configparser
一些新的模塊合併了多箇舊的模塊,若是你須要的東西是來自多箇舊模塊那上上面的狀況將不能工做。你也能夠不用那些有子模塊的模塊來這些。import httplib as http.client是一個語法錯誤。urllib和urllib2不只僅是合併,也整合進了幾個子模塊。因此你須要分別導入每個你用到的模塊名。這經意味着你一樣須要修改你的代碼,在Python 2你能夠像這樣檢索一個網頁:
>>> import urllib >>> import urlparse >>> >>> url = 'http://docs.python.org/library/' >>> parts = urlparse.urlparse(url) >>> parts = parts._replace(path='/3.0'+parts.path) >>> page = urllib.urlopen(parts.geturl())
在2to3轉換以後它看起來是這樣:
>>> import urllib.request, urllib.parse, urllib.error >>> import urllib.parse >>> >>> url = 'http://docs.python.org/library/' >>> parts = urllib.parse.urlparse(url) >>> parts = parts._replace(path='/3.0'+parts.path) >>> page = urllib.request.urlopen(parts.geturl())
是的,urllib.parse會被引用兩次而且urllib.error雖然被引用可是沒有被使用。這就是固定是如何作的,它作了不少額外的努力,因此它導入的比須要的更多。咱們須要修復代碼使咱們直接使用的名字來取代模塊的位置,因此能同時在Python 2和Python 3下同時運行的版本最終看起來像這樣:
>>> try: ... from urllib.request import urlopen ... from urllib.parse import urlparse ... except ImportError: ... from urlparse import urlparse ... from urllib import urlopen ... >>> url = 'http://docs.python.org/library/' >>> parts = urlparse(url) >>> parts = parts._replace(path='/3.0'+parts.path) >>> page = urlopen(parts.geturl())
在Python 3的整數處理上有兩個大變化。第一個是int和long類型被合併。這意味着你不能再經過添加L結尾來指定整數是long類型。1L在Python 3是一個語法錯誤。
若是你必定要在Python 2中使用長整形整數而且兼容Python 3,下面的代碼能夠作到。它在Python 3定義了一個跟int類同樣的long變量,而且它以後能夠被用於明顯地確保整數是一個長整形。
>>> import sys >>> if sys.version_info > (3,): ... long = int >>> long(1) 1L
另外一個變化是八進制字面量語法的變化。在Python 2一個0開始的數字是八進制,其中若是你打算用零來開始一個數字時會產生混淆。在Python 3這個語法換成了更不混淆的0o,類似的0x用於十六進制數字。用0開始一個數字是一個語法錯誤,這防止了你錯誤地使用舊式八進制語法。
八進制幾乎只用於Unix下的權限設置,但這又是一個至關常見的任務。有一個能同時在Python 2和Python 3工做的簡單方式:使用十進制或者十六進制值並把這個八進制值放進一個命令裏:
>>> f = 420 # 在八進制是644, 'rw-r--r--'
咱們在不用2to3同時支持Python 2和Python 3時遇到的最棘手問題是處理二進制數據,這一點也不奇怪,就像使用2to3時同樣。當大家選擇不用2to3時這個問題通Unicode問題產生了更多了麻煩,當使用2to3來支持Python 2和Python 3時2to3會把Unicode文字轉換成直接的字符串文字。不使用2to3我就沒有這種愉快而且由於Un文字u''在Python 3已經消失咱們須要找到一種方式來講咱們想要一種能在全部Python版本下工做的Unicode字符串。
這裏,只支持Python 3.3可讓事情更容易不少,由於在Python 3.3u''文字回來了。在這種狀況下,你幾乎能夠乎略這部分。
可是若是你須要支持Python 3.1或者3.2,要作這個的最好方式是寫一個像在常見遷移問題中的b()函數那樣只是使用Unicode字符串代替二進制字節的Unicode字符串產生函數。給這個函數的自然名字固然是u()。而後咱們使用b()來代替b''文字,而且使用u()來取代u''文字。
import sys if sys.version_info < (3,): import codecs def u(x): return codecs.unicode_escape_decode(x)[0] else: def u(x): return x
這將會在Python 2中返回一個unicode對象:
>>> from makeunicode import u >>> u('GIF89a') u'GIF89a'
可是它在Python 3返回一個字符串對象:
>>> from makeunicode import u >>> u('GIF89a') 'GIF89a'
這裏我使用unicode_escape編碼,由於若是你用和函數指定編碼不一樣的編碼來保存文件時其它的編碼會失敗。unicode_escape在輸入字符和保存文件上多作了一些工做,可是它會在不一樣的Python版本以及不一樣的操做系統平臺上工做。
unicode_escape編碼會轉換輸入unicode字符的全部版本的方式。'\x00' 語法,'\u0000' 甚至'\N{name}'語法:
>>> from makeunicode import u >>> print(u('\u00dcnic\u00f6de')) Ünicöde >>> print(u('\xdcnic\N{Latin Small Letter O with diaeresis}de')) Ünicöde
若是你只須要支持Python 2.6或者以後的版本,也可使用from __future__ import unicode_literals。這能夠把文件中的全部字符串文字轉成Unicode文字:
>>> from __future__ import unicode_literals >>> type("A standard string literal") <type 'unicode'> >>> type(b"A binary literal") <type 'str'>
在Python 2下全部帶有__future__導入及u()函數的二進制數據類型被叫着str而且文本類型被稱爲unicode,然而在Python 3下他被稱爲bytes和str。
繞過這個的最好辦法是定義根據不一樣的Python版本兩個變量:text_type 和binary_type,而後再測試一次這些變量。
>>> from __future__ import unicode_literals >>> import sys >>> if sys.version_info < (3,): ... text_type = unicode ... binary_type = str ... else: ... text_type = str ... binary_type = bytes >>> isinstance('U\xf1ic\xf6de', text_type) True
對於處理二進制數據你可使用在《常見的遷移問題》中討論的相同的技巧。
Python 2和Python 3之間有不少不少更不一樣尋常而且有時候很微妙的不一樣。雖然這裏提到的技巧也能適合大多數這些不一樣意,我仍是強烈推薦Benjamin Peterson的模塊「six」[1]。它包含一個使用於檢查Python版本的PY3常量,而且它包含前面提到的b()和u()函數,並且u()函數沒有指定一個編碼,因此你只能使用ASCII字符。它也包含不少像text_type和 binary_type這樣有幫助的常量以及能同時在Python 2和Python 3下同時工做的print_()函數。
它還包含大量重組標準庫的的導入,因此取代這章開始中的try/except結構的是你能夠用導入six模塊中的模塊來代替。值得注意的是不支持重組的urllib和urllib2模塊,因此你仍是須要使用try/except導入技巧。
six模塊甚至包含不尋常問題的助理,例如使用已經被更名的元類和函數的屬性。雖然它須要Python 2.4及以後的版本,但若是須要的話你能夠在更早的Python版本下使用許多它裏的技巧。
若是你試圖不轉換支持Python 2和Python 3,你必定會發現它頗有幫助。
附註:
[1] | http://pypi.python.org/pypi/six |
本文地址:http://my.oschina.net/soarwilldo/blog/522927
在湖聞樟注:
原文http://python3porting.com/noconv.html