Supporting Python 3(支持python3)——为Python 3做准备
2019獨角獸企業重金招聘Python工程師標準>>>
為Python3作準備
在開始添加Python 3的支持前,為了能夠盡可能地順利過度到Python 3,你應該通過修改對2to3來說很難苦的東西來給你的代碼做一些準備。即使你現在不打算遷移到Python 3,有一些事你也可以現在就做,這些事在一些情況下它們甚至會加快你的代碼在Python 2下的運行。
你可能想要讀在用現代的用句來改善你的代碼 上包含許多其他一些你能夠用到你的代碼中的改進的章節。
在Python 2.7下運行
這個過程的第一步是讓你的代碼在Python 2.6或者2.7下運行。在這里你用的是什么版本不重要,但是明顯最后的Python 2是很有意義的,所以如果你能用Python 2.7的話,就用吧。
大多數代碼不用修改就可以直接運行,但是從Python 2.5到2.6有幾個變化。在Python 2.6 as 和 with 是關鍵字,所以如果你使用這些作為變量就需要修改他們。最簡單的辦法是在變量的尾部加上下劃線。
>>>?with_?=?True >>>?as_?=?False你還需要擺脫字符串異常。 使用字串符來拋出異常已經不被推薦很長時間了,主要是因為他們非常不靈活,例如你不能繼承他們。
>>>?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 做到的。
雖然把除號前的一個操作數中轉換成浮點數能很好地工作,但是在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根本沒有辦法知道數據是文本還是二進制數據。
當排序時,使用key來代替cmp
在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?NotImplementeddef?__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()。以及最重要的是如它不能被比較,如果其他對象如何去比較兩個對象,運算符會備用地尋問其他對象。這樣你就擁有了一個有最大機會進行有意義比較的對象。
實現 __hash__()
在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,如果有話你必須要盡可能替換成現在對應的。
在 被移除的模塊 上有一個被移除模塊的列表。
測試覆蓋率和tox
有一個好的測試集對任何項目都是有價值的。當你添加Python 3支持時,測試可以把這個過程加快很多,因為你需要一遍又一遍地執行測試而手工測試要花掉大量時間。
用更多的測試來提高測試覆蓋率總是一個好主意。最流行的獲得你的模塊的測試覆蓋率的Python 工具是Ned Batchelder的coverage 模塊。[2] 許多像zope.testing、nose及py.test這樣的測試運行框架都包含對coverage模塊的支持,所以你可能已經安裝了。
如果你在開發一個支持所有版本Python的模塊,為所有這些版本執行測試迅速會變成一個令人厭煩的工作。為了解決這個Holger Krekel制作了一個叫做tox[3]的工具,這個工具會為每一個你想要支持的版本安裝虛擬環境并且使用一個簡單的命令對所有這些版本執行你的測試。它似乎是一件小事,它是的,但是它會增加一點點更愉快的體驗。如果你計劃同時支持Python 2和Python 3,你應該試試看。
可選:在字典中使用迭代器方法(iterator-methods)
從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
引導頁Supporting Python?3:(支持Python3):深入指南
目錄Supporting Python?3(支持Python 3)——目錄
轉載于:https://my.oschina.net/soarwilldo/blog/506415
總結
以上是生活随笔為你收集整理的Supporting Python 3(支持python3)——为Python 3做准备的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTML特殊字符大全2
- 下一篇: 迷你世界万能土块怎么弄