在開始添加Python 3的支持前,爲了可以儘量地順利過分到Python 3,你應該經過修改對2to3來講很難苦的東西來給你的代碼作一些準備。即便你如今不打算遷移到Python 3,有一些事你也能夠如今就作,這些事在一些狀況下它們甚至會加快你的代碼在Python 2下的運行。html
你可能想要讀在用現代的用句來改善你的代碼 上包含許多其餘一些你可以用到你的代碼中的改進的章節。python
這個過程的第一步是讓你的代碼在Python 2.6或者2.7下運行。在這裏你用的是什麼版本不重要,可是明顯最後的Python 2是頗有意義的,因此若是你能用Python 2.7的話,就用吧。算法
大多數代碼不用修改就能夠直接運行,可是從Python 2.5到2.6有幾個變化。在Python 2.6 as 和 with 是關鍵字,因此若是你使用這些做爲變量就須要修改他們。最簡單的辦法是在變量的尾部加上下劃線。安全
>>> with_ = True >>> as_ = False
你還須要擺脫字符串異常。 使用字串符來拋出異常已經不被推薦很長時間了,主要是由於他們很是不靈活,例如你不能繼承他們。app
>>> raise "Something went wrong!" Traceback (most recent call last): ... Something went wrong!
在Python 3字符串異常已經徹底消失了。 在Python 2.6中你不能發出他們,可是爲了向後兼容你仍是可能捕捉到他們。在一些狀況下你須要在你的代碼中移除全部字符串異常的使用而且在作任何事以前先讓他在Python 2.6下運行。框架
>>> raise Exception("Something went wrong!") Traceback (most recent call last): ... Exception: Something went wrong!
下一步是在Python 2.6和Python 2.7下用 -3選項來運行你的代碼。這個選項會對那個在Python 3中不支持及2to3不能轉換的部分發出警告。它主要針對的是那麼已經被棄用好久的和有新替代方式的部分,或者要被從標準庫中移除的模塊。例如對Classic Mac OS的支持已經被移除了,如今只支持OS X而且由於這個緣由對Classic Mac OS特殊特性的支持模塊已經被移除了。函數
你會獲得一些下面列出的這麼變化以及一些被重組庫的警告(但不是所有)。 庫的重組變化是很簡單的,不須要解釋,警告會提醒你新模塊名字。工具
是Python 2除兩個整數將反回兩個整數。那意味着5除以2將返回2。測試
>>> 5/2 2
可是,在Python 3這會返回2.5。大數據
>>> 5/2 2.5
今天在Python 2中的除塵運行符相信整除相除返回的是整數。可是使用2to3自動轉換時會不知道操做數的類型是什麼 ,因些它不知道除法操做是否是除以整數。 所以它不會在這裏作任何轉換。這意味着若是你正在使用舊的整數除法操做,你的代碼在Python 3下會有不少錯誤。
因爲這個變化從Python 2.2就開始計劃了,Python 2.2及以後的版本都包含一個叫着浮點除法的使用兩個斜線的新操做。 即便有浮點數,它也老是返回整數。任何你真的想要使用返回完整數字的浮點除法的地方,都應該把除塵操做改爲浮點除法操做。
>>> 5//2 2 >>> 5.0//2.0 2.0
一般狀況下,Python 2的除法操做是沒必要要的。解決這個問題的最多見的方法是把其中一個整數轉換成浮點數或者在其中一個數字上添加一個小數點。
>>> 5/2.0 2.5 >>> a = 5 >>> b = 2 >>> float(a)/b 2.5
可是,有一個更合適的辦法,那就是容許使用Python 3的行爲。這是經過從Python 2.2開始可能使用的__future__ import 作到的。
>>> from __future__ import division >>> 5/2 2.5
雖然把除號前的一個操做數中轉換成浮點數能很好地工做,可是在Python 3中不是沒有必要的而且使用__future__ import就能夠避免它。
使用帶 -3 選項的Python 2.6運行若是你用舊的整數除法的話就會提醒。
在Python 2有兩種類,「舊式」和「新式」。「舊式」的類已經在Python 3中移除了,所以即便沒有顯示地定義全部的類也都是object(對象)的子類。
在新舊的類上有一些不一樣,可是隻有不多的狀況下他們纔會在Python 3下產生問題。若是你使用了多重繼承可能會由於不一樣解決順序遇到一些問題。[4]
若是你用了多重繼承,所以你應該在添加Python 3支持前切換成新式類。 經過確保全部的對象是object(對象)的子類來作到這個。而且你可能必需要在類定義中改變列出的超類順序。
在Python 2,用str對象來保存二進制數據和ASSCII文本,然而保存在unicode的文件數據比能夠保存在ASCII的須要更多字符。在Python 3,取代str和unicode對象的是你能夠用bytes對象保存二進制數據而且使用str對象保存無論是否是Unicode的全部類型文本數據。即便有明顯的差別,Python 3的str類型或多或少相似於Python 2的unicode類型,而bytes類型頗爲相似Python 2的str類型。
爲這個做準備的第一步是確保沒有在二進制和文本數據上使用相同的變量名。在Python 2這不會給你帶來麻煩,可是在Python 3會的,所以儘量保證二進制數據和文本是分開的。
在Python 2上,「t」和「b」文件模式標識志能夠改變在一些平臺(例如Windows)上換行是如何處理的。可是這些標誌在Unix上不會產生不一樣做用,所以爲Unix開發的不少項目每每會乎略那些標誌而且在文本模式打開二進制文件。然而在Python 3 這些標誌也決定了當你從文件中讀數據返回的結果是bytes對象仍是unicode對象。雖然文本標誌是默認的,可是必定要添加上它,由於那表示本文模式是有目的的並不僅是你忘記加標誌。
帶上-3選項執行Python 2.6是不會提醒這個問題的,由於Python 2根本沒有辦法知道數據是文本仍是二進制數據。
在Python 2帶排序方法能夠帶一個cmp參數,這個參數是一個比較兩個值並返回-1,0或者1的函數。
>>> def compare(a, b): ... """Comparison that ignores the first letter""" ... return cmp(a[1:], b[1:]) >>> names = ['Adam', 'Donald', 'John'] >>> names.sort(cmp=compare) >>> names ['Adam', 'John', 'Donald']
從Python 2.4開,.sort()和新的sorted()函數同樣(見使用sorted()代替.sort()),sorted()能夠帶一個返回排序鍵值的函數做爲key參數。
>>> def keyfunction(item): ... """Key for comparison that ignores the first letter""" ... return item[1:] >>> names = ['Adam', 'Donald', 'John'] >>> names.sort(key=keyfunction) >>> names ['Adam', 'John', 'Donald']
這使用真情更容易而且執行起來更快。當使用cmp參數時,排序比較每一對值,所以對每一個項目都要屢次調用比較函數。一個更大的數據集合將要爲每個項目調用更屢次的比較函數。使用key函數,排序使用保存每個項目的鍵值並比較他們來取代,因些每個項目只調用一次key函數。由於這個緣由比較大數據集合時會快不少。
key函數每每能夠用如此簡單地用lambda來替代:
>>> names = ['Adam', 'Donald', 'John'] >>> names.sort(key=lambda x: x[1:]) >>> names ['Adam', 'John', 'Donald']
Python 2.4 還引入了reverse參數。
>>> names = ['Adam', 'Donald', 'John'] >>> names.sort(key=lambda x: x[1:], reverse=True) >>> names ['Donald', 'John', 'Adam']
在你排序的是幾個值時,使用key跟使用cmp相較起來差距不是很明顯。比方說,咱們想要首先按名字的長度排序而且相同長度的按字母排序。 用key函數作這個並不顯而易見,可是解決方法一般先按次要求排序以後再按另外一個要求排序。
>>> names = ['Adam', 'Donald', 'John'] >>> # Alphabetical sort >>> names.sort() >>> # Long names should go first >>> names.sort(key=lambda x: len(x), reverse=True) >>> names ['Donald', 'Adam', 'John']
這個能夠工做是由於從Python 2.3開始採用了timsort排序算法[1]。這是一個平穩的算法,這意味着若是比較的兩個項目是相同的它將會保留他們原來的順序。
你也能夠寫一個能返回結合了兩個鍵值的值的key函數而且一鼓作氣地完成排序。使人驚奇的是這不必定會更快,你須要測試下哪個解決方案在你的狀況下會更快,這取決於數據和key函數。
>>> def keyfunction(item): ... """Sorting on descending length and alphabetically""" ... return -len(item), item >>> names = ['Adam', 'Donald', 'John'] >>> names.sort(key=keyfunction) >>> names ['Donald', 'Adam', 'John']
key參數在Python 2.4引入的,因此若是你想要支持Python 2.3就不能用它了。若是你想要用key函數作不少的排序,最好的辦法是爲Python 2.3實現一個簡單的在Python 2.4及以後版本的sorted()函數而且用那個來替代內置的sorted()。
>>> import sys >>> if sys.version_info < (2, 4): ... def sorted(data, key): ... mapping = {} ... for x in data: ... mapping[key(x)] = x ... keys = mapping.keys() ... keys.sort() ... return [mapping[x] for x in keys] >>> data = ['ant', 'Aardvark', 'banana', 'Dingo'] >>> sorted(data, key=str.lower) ['Aardvark', 'ant', 'banana', 'Dingo']
Python 2.4如今已經有5年那麼老了,因此你不太可能須要支持Python 2.3。
警告
使用-3選項運行Python只會在你顯示地使用cmp參數時警告你:
>>> l.sort(cmp=cmpfunction) __main__:1: DeprecationWarning: the cmp argument is not supported in 3.x
可是若是像下面這樣使用將不警告:
>>> l.sort(cmpfunction)
因此這個語法可能會漏網之魚。在Python 3運行這些代碼時,在這些狀況下你會獲得一個TypeError: mustuse keyword argument for key function 。
在Python 2.7和Python 3.2及後面的版本有一個函數能夠經過一個包裝類把比較函數轉換成key函數。它是很聰明的,但反而會讓比較函數更慢,所以這只是最後的手段。
>>> from functools import cmp_to_key >>> def compare(a, b): return cmp(a[1:], b[1:]) >>> sorted(['Adam', 'Donald', 'John'], key=cmp_to_key(compare)) ['Adam', 'John', 'Donald']
在Python 2最多見的支持對象比較和排序的方式是實現一個使用內置cmp()函數的__cmp__()方法,像這樣類就能夠根據姓氏排序了:
>>> class Orderable(object): ... ... def __init__(self, firstname, lastname): ... self.first = firstname ... self.last = lastname ... ... def __cmp__(self, other): ... return cmp("%s, %s" % (self.last, self.first), ... "%s, %s" % (other.last, other.first)) ... ... def __repr__(self): ... return "%s %s" % (self.first, self.last) ... >>> sorted([Orderable('Donald', 'Duck'), ... Orderable('Paul', 'Anka')]) [Paul Anka, Donald Duck]
然則,你能夠擁有相似colors這樣的即不能比較大小但能夠比較是否相等的類,所以從Python 2.1開始也支持一個比較操做符對應一個方法的豐富的比較方法。他們是 __lt__ 對應 <,__le__ 對應<=, __eq__ 對應==, __ne__ 對應!=, __gt__對應> ,以及 __ge__ 對應 >=。
同時擁有豐富的比較方法和__cmp__()方法違反了只有一種方式來實現比較的原則,因此在Python 3對__cmp__()的支持已經被移除了。所以對Python 3,若是你的類須要被比較的話,你必需要全部的豐富比較操做符。 沒有必要在開始支持Python 3作這個,但作這些是一種體驗。
寫比較方法會至關棘手,由於你可能須要處理不一樣類型的比較。若是比較函數不知道如何與其餘對象比較時會返回NotImplemented常量。返回的NotImplemented 能夠做爲一個Python的比較標誌來讓Python償試反向比較。因此若是你的__lt__()方法返回NotImplemented那麼Python會償試用調用其餘類的__gt__()方法來代替。
注意
這意味着你永遠都不該該在你的豐富比較方法中調用其餘類的比較操做!你會找到幾個轉換大於(就像self.__gt__(other))成返回other < self的豐富比較助手的例子。可是你調用other.__lt__(self)卻會返回NotImplemented而不是再次償試self.__gt__(other)而且無限遞歸。
一旦你理解了全部案例,實現一個好的正解運行的豐富比較操做集不會困難,可是掌握那些卻不是徹底不重要。你能夠用許多不一樣的方式作它,個人首選方式是這樣混合,這樣就能夠同時在Python 2和Python 3很好地工做。
class ComparableMixin(object): def _compare(self, other, method): try: return method(self._cmpkey(), other._cmpkey()) except (AttributeError, TypeError): # _cmpkey not implemented, or return different type, # so I can't compare with "other". return NotImplemented def __lt__(self, other): return self._compare(other, lambda s, o: s < o) def __le__(self, other): return self._compare(other, lambda s, o: s <= o) def __eq__(self, other): return self._compare(other, lambda s, o: s == o) def __ge__(self, other): return self._compare(other, lambda s, o: s >= o) def __gt__(self, other): return self._compare(other, lambda s, o: s > o) def __ne__(self, other): return self._compare(other, lambda s, o: s != o)
前面提到的Ptyhon 3.2的functools.total_ordering()類裝飾器也是一個很好的解決辦法,而且它一樣能夠用複製並用在其它版本上。可是由於它用的類裝飾器,因此不能在Python 2.6如下版本使用。
使用前面的混合,你須要實現返回能一反被比較的對象鍵值的_cmpkey()方法,相似於比較時用的key()函數。實現真情能夠相似這樣:
>>> from mixin import ComparableMixin >>> class Orderable(ComparableMixin): ... ... def __init__(self, firstname, lastname): ... self.first = firstname ... self.last = lastname ... ... def _cmpkey(self): ... return (self.last, self.first) ... ... def __repr__(self): ... return "%s %s" % (self.first, self.last) ...>>> sorted([Orderable('Donald', 'Duck'), ... Orderable('Paul', 'Anka')]) [Paul Anka, Donald Duck]
若是對象比較時沒有實現 _cmpkey()方法或者前面的混合使用self._cmpkey()的返回值返回的值不能被比較,前面的混合將會返回NotImplemented。 這意味着每個對象都要有一個能返回能合其餘有能返回一個元組的_cmpkey()的對象比較的_cmpkey()。以及最重要的是如它不能被比較,若是其餘對象如何去比較兩個對象,運算符會備用地尋問其餘對象。這樣你就擁有了一個有最大機會進行有意義比較的對象。
在Python 2,若是你想實現 __eq__() 你也須要重寫__hash__()。這是由於兩個對象比較趕快相等也須要相同的哈希值。 若是對象是可變的,你必需要把設置__hash__成None來把它標定成可變的。這意味着你不能把它用做字典的鍵值, 這很好,只有不可變對象能夠作字典鍵。
在Python 3,若是你定義了__eq__(), __hash__ 會被自動設置成, 而且對象變成不能被哈希。因此對於Python 3,除非是一個不變的對象或者你想把它做爲一個鍵值,你都不須要重寫__hash__()。
被__hash__()返回的值須要是一個整數,兩個比較相等的對象必定有相同的哈希值。它必須在對象的整個存活期內保持不變,這也是爲何可變對象爲何必須設置__hash__ = None來標記它們是不能哈希的。
若是你使用前面說起的實現比較運算符的_cmpkey()方法,那麼不實現__hash__()是很容易的:
>>> from mixin import ComparableMixin >>> class Hashable(ComparableMixin): ... def __init__(self, firstname, lastname): ... self._first = firstname ... self._last = lastname ... ... def _cmpkey(self): ... return (self._last, self._first) ... ... def __repr__(self): ... return "%s(%r, %r)" % (self.__class__.__name__, ... self._first, self._last) ... ... def __hash__(self): ... return hash(self._cmpkey()) ... >>> d = {Hashable('Donald', 'Duck'): 'Daisy Duck'} >>> d {Hashable('Donald', 'Duck'): 'Daisy Duck'}
這個類屬性按照慣例使用前導的下劃線來標記成內部使用,但他們不是傳統意義上的不可變。若是你想要一個在Python中真正的不可變類,最簡單的辦法就是繼承collections.namedtuple,但那超出了本書的範疇。
不少在標準庫中的模塊在Python 3中已經被丟棄了。他們中的多大數是對不提供被新模塊支持的更好接口的舊系統的特別支持。
若是你使用了一些更經常使用的模塊,使用-3選項運行Python 2.6 將會警告你。你使用了那些Python 2.6不會發出警告的模塊是至關不可能的,可是若是你正在或者計劃同時支持Python 2和Python 3,若是有話你必需要儘量替換成如今對應的。
在 被移除的模塊 上有一個被移除模塊的列表。
有一個好的測試集對任何項目都是有價值的。當你添加Python 3支持時,測試能夠把這個過程加快不少,由於你須要一遍又一遍地執行測試而手工測試要花掉大量時間。
用更多的測試來提升測試覆蓋率老是一個好主意。最流行的得到你的模塊的測試覆蓋率的Python 工具是Ned Batchelder的coverage 模塊。[2] 許多像zope.testing、nose及py.test這樣的測試運行框架都包含對coverage模塊的支持,因此你可能已經安裝了。
若是你在開發一個支持全部版本Python的模塊,爲全部這些版本執行測試迅速會變成一個使人厭煩的工做。爲了解決這個Holger Krekel製做了一個叫作tox[3]的工具,這個工具會爲每個你想要支持的版本安裝虛擬環境而且使用一個簡單的命令對全部這些版本執行你的測試。它彷佛是一件小事,它是的,可是它會增長一點點更愉快的體驗。若是你計劃同時支持Python 2和Python 3,你應該試試看。
從Python 2.2開始內置的字典類型有iterkeys()、 itervalues() 、 iteritems() 方法。他們產生的數據像是keys()、values() 和items()產生的,但他們返回的不是列表而是迭代器(iterator),在使用巨大的字典時這能夠節省時間和內存。
>>> dict = {'Adam': 'Eve', 'John': 'Yoko', 'Donald': 'Daisy'} >>> dict.keys() ['Donald', 'John', 'Adam'] >>> dict.iterkeys() <dictionary-keyiterator object at 0x...>
在Python 3標準的keys(),、values() 和 items()返回和迭代器很是相似的字典視圖。由於不久以後在這些方法中的迭代器變量要被移除。
2to3 會把迭代器方法的使用轉換成標準方法。經過明確地使用迭代器方法,你代表不須要一個列表,這對2to3的轉換是有幫助的,不然爲了安全要用alist(dict.values())來取代dict.values()調用。
Python 2.7也有像.viewitems()、.viewkeys() 和 .viewvalues()這樣可用的新視圖迭代器,但由於他們在更早的Python版本中不存在因此他們只有在你打算放棄Python 2.6及更早的版本時纔有用。
還要注意的是若是你的代碼依靠返回的列表,那麼你可能誤用未知的字典。例如:下面的代碼,你事實上不能肯定每次的鍵的順序是相同的,由於這個緣由你可能沒法預測該代碼的行爲。這在調試時會帶來一些麻煩。
>>> dict = {'Adam': 'Eve', 'John': 'Yoko', 'Donald': 'Daisy'} >>> dict.keys() [0]'Donald'
記住,若是你想要遍歷字典就使用for x in dict,這樣在Python 2和Python 3中都會自動使用迭代器。
>>> dict = {'Adam': 'Eve', 'John': 'Yoko', 'Donald': 'Daisy'} >>> for x in dict: ... print '%s + %s == True' % (x, dict[x]) Donald + Daisy == True John + Yoko == True Adam + Eve == True
附註
[1] | http://en.wikipedia.org/wiki/Timsort |
[2] | https://pypi.python.org/pypi/coverage |
[3] | http://testrun.org/tox/latest/ |
[4] | 見 http://www.python.org/download/releases/2.2.3/descrintro/#mro |
在湖聞樟注:
原文http://python3porting.com/preparing.html