編寫兼容 Python 2.x 和 3.x 代碼的方法

編寫兼容Python2.x與3.x代碼


當我們正處於Python 2.x到Python 3.x的過渡期時,你可能想過是否可以在不修改任何代碼的前提下能同時運行在Python 2和3中。這看起來還真是一個合理的訴求,但如何開始呢?哪些Python 2 代碼在 3.x 解釋器執行時容易出狀況呢?


print vs print()


如果你想的和我一樣,你或許會說print語句,這是個很好的着手點,先簡單展示一下,print在2.x中是一條語句,而在3.x中它是一個關鍵字或者是保留字。換句話說,因爲這個變化涉及到語言的語法,你不可以使用在if語句中,Python仍然沒有#ifdef 宏。下面嘗試把括號裏面的參數打印出來:


>>> print('Hello World!')

Hello World!


很酷,這個在Python2和Python3中都可以運行,而且運行的效果是一樣的,再來看看下面這段:


>>> print(10, 20) # Python 2

(10, 20)


此時,你並沒有像前面那樣幸運得到一樣的結果,Python2中打印的是元組(tuple),而在Python3中傳遞多個參數到print()裏面時打印的是兩個值:


>>> print(10, 20) # Python 3

10 20


如果你思考得比較多的話,我們可以檢查print是否是一個關鍵字,keyword模塊包含一個關鍵字列表。print在3.x中不是關鍵字,可以簡單驗證一下:


>>> import keyword

>>> 'print' in keyword.kwlist

False


作爲一名聰明的程序員,你可能在2.x中嘗試的時候期待的結果是True,儘管這並沒有錯,但是爲了達到Python3的效果,但你仍然會因爲其他原因導致失敗。


>>> import keyword

>>> if 'print' in keyword.kwlist:

...     from __future__ import print_function

...

File "", line 2

SyntaxError: from __future__ imports must occur at the beginning of the file


一種解決方案是使用一個函數,其功能類似於print,其中之一是sys.stdout.write(),另一個是distutils.log.warn()。不管出於什麼原因,我們決定使用後者。“hello world”的例子看起來是這樣的:


# Python 2.x

print 'Hello World!'

# Python 3.x

print('Hello World!')


下面的代碼就可以在兩個版本中通用:


# Python 2.x & 3.x compatible

from distutils.log import warn as printf

printf('Hello World!')


爲什麼我們不用sys.stdout.write()呢,因爲我們需要添加一個NEWLINE字符在字符串的結尾來兼容這種行爲(python2.x中write方法不會換行):


# Python 2.x & 3.x compatible

import sys

sys.stdout.write('Hello World!n')


Import your way to a solution


一般情況情況下,import時沒什麼煩惱,只要正確的導入就行,但在下面代碼中,我們想導入urlopen()函數,在Python2中,他同時存在與urllib2和urllib2中(我們使用後者),在Python3中,他被集成到了urllib.request中,而你的方案是要既能在2.x和3.x中正常工作:


try:

    from urllib2 import urlopen

except ImportError:

    from urllib.request import urlopen


出於對內存的保護,也許你對iterator(Python3)版本的zip()更加有興趣,在Python2中,iterator版本是itertools.izip()。這個函數在Python3中被重命名替換成了zip()。如果你使用迭代版本,導入語句也非常直白:


try:

    from itertools import izip as zip

except ImportError:

    pass


另一個列子是看來來並不怎麼優雅的StringIO類,在Python2中,純Python版本是StringIO模塊,意味着訪問的時候是通過StringIO.StringIO,同樣還有一個更爲快速的C語言版本,位於cStringIO.StringIO,不過這取決你的Python安裝版本,你可以優先使用cStringIO然後是StringIO(如果cStringIO不能用的話)。在Python3中,Unicode是默認的string類型,但是如果你做任何和網絡相關的操作,很有可能你不得不用ASCII/字節字符串來操作,所以代替StringIO,你要io.BytesIO,爲了達到你想要的,這個導入看起來有點醜:


try:

    from io import BytesIO as StringIO

except ImportError:

    try:

        from cStringIO import StringIO

    except ImportError:

        from StringIO import StringIO


Putting it all together


如果你運氣好的話,上面那些就是你要準備做的全部,剩下的代碼都比開始設置的地方更簡單。如果你按照上面的方式導入了distutils.log.warn()[printf()],url*urlopen(),*.StringIO和一個標準的導入:xml.etree.ElementTree(2.5及更新的),現在你就可以寫一個非常簡短短的解析器來展示從Google News服務中提供的頭條故事(譯註:當然首先得備一個×××),只需八行代碼:


g = urlopen('http://news.google.com/news?topic=h&output=rss')

f = StringIO(g.read())

g.close()

tree = xml.etree.ElementTree.parse(f)

f.close()

for elmt in tree.getiterator():

    if elmt.tag == 'title' and not

            elmt.text.startswith('Top Stories'):

        printf('- %s' % elmt.text)


這段腳本在2.x和3.x下面運行時,不需要做任何改動,運行效果完全一樣,當然,如果你正在使用的是2.4或者更老的版本,你需要單獨下載ElementTree。


但是有時候感覺這些改變把你優雅的Python代碼弄得一團糟,畢竟可讀性纔是最重要的,如果你要優先保證代碼的整潔而且在不修改任何地方的前提下運行在兩個版本的Python環境中,那麼你可以看一下six包。


six一個兼容庫,它的主要任務是提供接口隱藏複雜的細節,你可以在這裏找到它。無論你是使用像six這樣的庫還是用自己的方法來做,我們希望這個簡短的介紹可以讓你開始考慮寫的代碼能夠在2.x和3.x下同時運行。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章