• <fieldset id="8imwq"><menu id="8imwq"></menu></fieldset>
  • <bdo id="8imwq"><input id="8imwq"></input></bdo>
    最新文章專題視頻專題問答1問答10問答100問答1000問答2000關鍵字專題1關鍵字專題50關鍵字專題500關鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關鍵字專題關鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
    問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
    當前位置: 首頁 - 科技 - 知識百科 - 正文

    把項目從Python2.x移植到Python3.x的經驗總結

    來源:懂視網 責編:小采 時間:2020-11-27 14:39:30
    文檔

    把項目從Python2.x移植到Python3.x的經驗總結

    把項目從Python2.x移植到Python3.x的經驗總結:經歷移植jinja2到python3的痛苦之后,我把項目暫時放一放,因為我怕打破python3的兼容。我的做法是只用一個python2的代碼庫,然后在安裝的時候用2to3工具翻譯成python3。不幸的是哪怕一點點的改動都會打破迭代開發。如果你選對了python的版本,你可以專
    推薦度:
    導讀把項目從Python2.x移植到Python3.x的經驗總結:經歷移植jinja2到python3的痛苦之后,我把項目暫時放一放,因為我怕打破python3的兼容。我的做法是只用一個python2的代碼庫,然后在安裝的時候用2to3工具翻譯成python3。不幸的是哪怕一點點的改動都會打破迭代開發。如果你選對了python的版本,你可以專

    經歷移植jinja2到python3的痛苦之后,我把項目暫時放一放,因為我怕打破python3的兼容。我的做法是只用一個python2的代碼庫,然后在安裝的時候用2to3工具翻譯成python3。不幸的是哪怕一點點的改動都會打破迭代開發。如果你選對了python的版本,你可以專心做事,幸運的避免了這個問題。

    來自MoinMoin項目的Thomas Waldmann通過我的python-modernize跑jinja2,并且統一了代碼庫,能同時跑python2,6,2,7和3.3。只需小小清理,我們的代碼就很清晰,還能跑在所有的python版本上,并且看起來和普通的python代碼并無區別。

    受到他的啟發,我一遍又一遍的閱讀代碼,并開始合并其他代碼來享受統一的代碼庫帶給我的快感。

    下面我分享一些小竅門,可以達到和我類似的體驗。

    放棄python 2.5 3.1和3.2

    這是最重要的一點,放棄2.5比較容易,因為現在基本沒人用了,放棄3.1和3.2也沒太大問題,應為目前python3用的人實在是少得可憐。但是你為什么放棄這幾個版本呢?答案就是2.6和3.3有很多交叉哦語法和特性,代碼可以兼容這兩個版本。

  • 字符串兼容。2.6和3.3支持相同的字符串語法。你可以用 "foo" 表示原生字符串(2.x表示byte,3.x表示unicode),u"foo" 表示unicode字符串,b"foo" 表示原生字符串或字節數組。

  • print函數兼容,如果你的print語句比較少,那你可以加上"from __future__ import print_function",然后開始使用print函數,而不是把它綁定到別的變量上,進而避免詭異的麻煩。

  • 兼容的異常語法。Python 2.6引入的 "except Exception as e" 語法也是3.x的異常捕捉語法。

  • 類修飾器都有效。這個可以用在修改接口而不在類結構定義中留下痕跡。例如可以修改迭代方法名字,也就是把 next 改成 __next__ 或者把 __str__ 改成 __unicode__ 來兼容python 2.x。

  • 內置next調用__next__或next。這點很有用,因為他們和直接調用方法的速度差不多,所以你不用考慮得太多而去加入運行時檢查和包裝一個函數。

  • Python 2.6 加入了和python 3.3接口一樣的bytearray類型。這點也很有用,因為2.6沒有 3.3的byteobject類型,雖然有一個內建的名字但那僅僅只是str的別名,并且使用習慣也有很大差異。

  • Python 3.3又加入了byte到byte和string到string的編碼與解碼,這已經在3.1和3.2中去掉了,很不幸,他們的接口很復雜了,別名也沒了,但至少更比以前的2.x版本更接近了。

  • 最后一點在流編碼和解碼的時候很有用,這功能在3.0的時候去掉了,直到3.3才恢復。


    沒錯,six模塊可以讓你走得遠一點,但是不要低估了代碼工整度的意義。在Python3移植過程中,我幾乎對jinja2失去了興趣,因為代碼開始虐我。就算能統一代碼庫,但還是看起來很不舒服,影響視覺(six.b('foo')和six.u('foo')到處飛)還會因為用2to3迭代開發帶來不必要的麻煩。不用去處理這些麻煩,回到編碼的快樂享受中吧。jinja2現在的代碼非常清晰,你也不用當心python2和3的兼容問題,不過還是有一些地方使用了這樣的語句:if PY2:。

    接下來假設這些就是你想支持的python版本,試圖支持python2.5,這是一個痛苦的事情,我強烈建議你放棄吧。支持3.2還有一點點可能,如果你能在把函數調用時把字符串都包裝起來,考慮到審美和性能,我不推薦這么做。

    跳過six

    six是個好東西,jinja2開始也在用,不過最后卻不給力了,因為移植到python3的確需要它,但還是有一些特性丟失了。你的確需要six,如果你想同時支持python2.5,但從2.6開始就沒必要使用six了,jinja2搞了一個包含助手的兼容模塊。包括很少的非python3 代碼,整個兼容模塊不足80行。

    因為其他庫或者項目依賴庫的原因,用戶希望你能支持不同版本,這是six的確能為你省去很多麻煩。

    開始使用Modernize

    使用python-modernize移植python是個很好的還頭,他像2to3一樣運行的時候生成代碼。當然,他還有很多bug,默認選項也不是很合理,可以避免一些煩人的事情,然你走的更遠。但是你也需要檢查一下結果,去掉一些import 語句和不和諧的東西。
    修復測試

    做其他事之前先跑一下測試,保證測試還能通過。python3.0和3.1的標準庫就有很多問題是詭異的測試習慣改變引起的。

    寫一個兼容的模塊

    因此你將打算跳過six,你能夠完全拋離幫助文檔么?答案當然是否定的。你依然需要一個小的兼容模塊,但是它足夠小,使得你能夠將它僅僅放在你的包中,下面是一個基本的例子,關于一個兼容模塊看起來是個什么樣子:

    import sys
    PY2 = sys.version_info[0] == 2
    if not PY2:
     text_type = str
     string_types = (str,)
     unichr = chr
    else:
     text_type = unicode
     string_types = (str, unicode)
     unichr = unichr

    那個模塊確切的內容依賴于,對于你有多少實際的改變。在Jinja2中,我在這里放了一堆的函數。它包括ifilter, imap以及類似itertools的函數,這些函數都內置在3.x中。(我糾纏Python 2.x函數,是為了讓讀者能夠對代碼更清楚,迭代器行為是內置的而不是缺陷) 。

    為2.x版本做測試而不是3.x

    總體上來說你現在正在使用的python是2.x版本的還是3.x版本的是需要檢查的。在這種情況下我推薦你檢查當前版本是否是python2而把python3放到另外一個判斷的分支里。這樣等python4面世的時候你收到的“驚喜”對你的影響會小一點

    好的處理:

    if PY2:
     def __str__(self):
     return self.__unicode__().encode('utf-8')

    相比之下差強人意的處理:

    if not PY3:
     def __str__(self):
     return self.__unicode__().encode('utf-8')

    字符串處理
    Python 3的最大變化毫無疑問是對Unicode接口的更改。不幸的是,這些更改在某些地方非常的痛苦,而且在整個標準庫中還得到了不一致地處理。大多數與字符串處理相關的時間函數的移植將完全被廢止。字符串處理這個主題本身就可以寫成完整的文檔,不過這兒有移植Jinja2和Werkzeug所遵循的簡潔小抄:

    'foo'這種形式的字符串總指的是本機字符串。這種字符串可以用在標識符里、源代碼里、文件名里和其他底層的函數里。另外,在2.x里,只要限制這種字符串僅僅可使用ASCII字符,那么就允許作為Unicode字符串常量。
    這個屬性對統一編碼基礎是非常有用的,因為Python 3的正常方向時把Unicode引進到以前不支持Unicode的某些接口,不過反過來卻從不是這樣的。由于這種字符串常量“升級”為Unicode,而2.x仍然在某種程度上支持Unicode,因此這種字符串常量怎么用都行。
    例如 datetime.strftime函數在Python2里嚴格不支持Unicode,并且只在3.x里支持Unicode。不過因為大多數情況下2.x上的返回值只是ASCII編碼,所以像這樣的函數在2.x和3.x上都確實運行良好。

     >>> u'Current time: %s' % datetime.datetime.utcnow().strftime('%H:%M')
     u'Current time: 23:52'

    傳遞給strftime的字符串是本機字符串(在2.x里是字節,而在3.0里是Unicode)。返回值也是本機字符串并且僅僅是ASCII編碼字符。 因此在2.x和3.x上一旦對字符串進行格式化,那么結果就一定是Unicode字符串。
    u'foo'這種形式的字符串總指的是Unicode字符串,2.x的許多庫都已經有非常好的支持Unicode,因此這樣的字符串常量對許多人來說都不應該感到奇怪。

    b'foo'這種形式的字符串總指的是只以字節形式存儲的字符串。由于2.6確實沒有類似Python 3.3所具有的字節對象,而且Python 3.3缺乏一個真正的字節字符串,因此這種常量的可用性確實受到小小的限制。當與在2.x和3.x上具有同樣接口的字節數組對象綁定在一起時候,它立刻變得更可用了。
    由于這種字符串是可以更改的,因此對原始字節的更改是非常有效的,然后你再次通過使用inbytes()封裝最終結果,從而轉換結果為更易讀的字符串。

    除了這些基本的規則,我還對上面我的兼容模塊添加了 text_type,unichr 和 string_types 等變量。通過這些有了大的變化:

  • isinstance(x, basestring) 變成 isinstance(x, string_types).

  • isinstance(x, unicode) 變成 isinstance(x, text_type).

  • isinstance(x, str) 為表明捕捉字節的意圖,現在變成 isinstance(x, bytes) 或者 isinstance(x, (bytes, bytearray)).

  • 我還創建了一個 implements_to_string 裝飾類,來幫助實現帶有 __unicode__ 或 __str__ 的方法的類:

    if PY2:
     def implements_to_string(cls):
     cls.__unicode__ = cls.__str__
     cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
     return cls
    else:
     implements_to_string = lambda x: x

    這個想法是,你只要按2.x和3.x的方式實現 __str__,讓它返回Unicode字符串(是的,在2.x里看起來有點奇怪),裝飾類在2.x里會自動把它重命名為 __unicode__,然后添加新的 __str__ 來調用 __unicode__ 并把其返回值用 UTF-8 編碼再返回。在過去,這種模式在2.x的模塊中已經相當普遍。例如 Jinja2 和 Django 中都這樣用。

    下面是一個這種用法的實例:

    @implements_to_string
    class User(object):
     def __init__(self, username):
     self.username = username
     def __str__(self):
     return self.username

    元類語法的更改
    由于Python 3更改了定義元類的語法,并且以一種不兼容的方式調用元類,所以這使移植比未更改時稍稍難了些。Six有一個with_metaclass函數可以解決這個問題,不過它在繼承樹中產生了一個虛擬類。對Jinjia2移植來說,這個解決方案令我非常 的不舒服,我稍稍地對它進行了修改。這樣對外的API是相同的,只是這種方法使用臨時類與元類相連接。 好處是你使用它時不必擔心性能會受影響并且讓你的繼承樹保持得很完美。
    這樣的代碼理解起來有一點難。 基本的理念是利用這種想法:元類可以自定義類的創建并且可由其父類選擇。這個特殊的解決方法是用元類在創建子類的過程中從繼承樹中刪除自己的父類。最終的結果是這個函數創建了帶有虛擬元類的虛擬類。一旦完成創建虛擬子類,就可以使用虛擬元類了,并且這個虛擬元類必須有從原始父類和真正存在的元類創建新類的構造方法。這樣的話,既是虛擬類又是虛擬元類的類從不會出現。
    這種解決方法看起來如下:

    def with_metaclass(meta, *bases):
     class metaclass(meta):
     __call__ = type.__call__
     __init__ = type.__init__
     def __new__(cls, name, this_bases, d):
     if this_bases is None:
     return type.__new__(cls, name, (), d)
     return meta(name, bases, d)
     return metaclass('temporary_class', None, {})
    下面是你如何使用它:
     
    class BaseForm(object):
     pass
     
    class FormType(type):
     pass
     
    class Form(with_metaclass(FormType, BaseForm)):
     pass

    字典
    Python 3里更令人懊惱的更改之一就是對字典迭代協議的更改。Python2里所有的字典都具有返回列表的keys()、values()和items(),以及返回迭代器的iterkeys(),itervalues()和iteritems()。在Python3里,上面的任何一個方法都不存在了。相反,這些方法都用返回視圖對象的新方法取代了。

    keys()返回鍵視圖,它的行為類似于某種只讀集合,values()返回只讀容器并且可迭代(不是一個迭代器!),而items()返回某種只讀的類集合對象。然而不像普通的集合,它還可以指向易更改的對象,這種情況下,某些方法在運行時就會遇到失敗。

    站在積極的一方面來看,由于許多人沒有理解視圖不是迭代器,所以在許多情況下,你只要忽略這些就可以了。
    Werkzeug和Dijango實現了大量自定義的字典對象,并且在這兩種情況下,做出的決定僅僅是忽略視圖對象的存在,然后讓keys()及其友元返回迭代器。

    由于Python解釋器的限制,這就是目前可做的唯一合理的事情了。不過存在幾個問題:

  • 視圖本身不是迭代器這個事實意味著通常狀況下你沒有充足的理由創建臨時對象。

  • 內置字典視圖的類集合行為在純Python里由于解釋器的限制不可能得到復制。

  • 3.x視圖的實現和2.x迭代器的實現意味著有大量重復的代碼。

  • 下面是Jinja2編碼庫常具有的對字典進行迭代的情形:

    if PY2:
     iterkeys = lambda d: d.iterkeys()
     itervalues = lambda d: d.itervalues()
     iteritems = lambda d: d.iteritems()
    else:
     iterkeys = lambda d: iter(d.keys())
     itervalues = lambda d: iter(d.values())
     iteritems = lambda d: iter(d.items())

    為了實現類似對象的字典,類修飾符再次成為可行的方法:

    if PY2:
     def implements_dict_iteration(cls):
     cls.iterkeys = cls.keys
     cls.itervalues = cls.values
     cls.iteritems = cls.items
     cls.keys = lambda x: list(x.iterkeys())
     cls.values = lambda x: list(x.itervalues())
     cls.items = lambda x: list(x.iteritems())
     return cls
    else:
     implements_dict_iteration = lambda x: x

    在這種情況下,你需要做的一切就是把keys()和友元方法實現為迭代器,然后剩余的會自動進行:

    @implements_dict_iteration
    class MyDict(object):
     ...
     
     def keys(self):
     for key, value in iteritems(self):
     yield key
     
     def values(self):
     for key, value in iteritems(self):
     yield value
     
     def items(self):
     ...

    通用迭代器的更改
    由于一般性地更改了迭代器,所以需要一丁點的幫助就可以使這種更改毫無痛苦可言。真正唯一的更改是從next()到__next__的轉換。幸運的是這個更改已經經過透明化處理。 你唯一真正需要更改的事情是從x.next()到next(x)的更改,而且剩余的事情由語言來完成。

    如果你計劃定義迭代器,那么類修飾符再次成為可行的方法了:

    if PY2:
     def implements_iterator(cls):
     cls.next = cls.__next__
     del cls.__next__
     return cls
    else:
     implements_iterator = lambda x: x
    為了實現這樣的類,只要在所有的版本里定義迭代步長方法__next__就可以了:
     
    @implements_iterator
    class UppercasingIterator(object):
     def __init__(self, iterable):
     self._iter = iter(iterable)
     def __iter__(self):
     return self
     def __next__(self):
     return next(self._iter).upper()

    轉換編解碼器
    Python 2編碼協議的優良特性之一就是它不依賴于類型。 如果你愿意把csv文件轉換為numpy數組的話,那么你可以注冊一個這樣的編碼器。然而自從編碼器的主要公共接口與字符串對象緊密關聯后,這個特性不再為眾人所知。由于在3.x里轉換的編解碼器變得更為嚴格,所以許多這樣的功能都被刪除了,不過后來由于證明轉換編解碼有用,在3.3里重新引入了。基本上來說,所有Unicode到字節的轉換或者相反的轉換的編解碼器在3.3之前都不可用。hex和base64編碼就位列與這些編解碼的之中。

    下面是使用這些編碼器的兩個例子:一個是字符串上的操作,一個是基于流的操作。前者就是2.x里眾所周知的str.encode(),不過,如果你想同時支持2.x和3.x,那么由于更改了字符串API,現在看起來就有些不同了:

    >>> import codecs
    >>> codecs.encode(b'Hey!', 'base64_codec')
    'SGV5IQ==
    '

    同樣,你將注意到在3.3里,編碼器不理解別名,要求你書寫編碼別名為"base64_codec"而不是"base64"。

    (我們優先選擇這些編解碼器而不是選擇binascii模塊里的函數,因為通過對這些編碼器增加編碼和解碼,就可以支持所增加的編碼基于流的操作。)

    其他注意事項
    仍然有幾個地方我尚未有良好的解決方案,或者說處理這些地方常常令人懊惱,不過這樣的地方會越來越少。不幸是的這些地方的某些現在已經是Python 3 API的一部分,并且很難被發現,直到你觸發一個邊緣情形的時候才能發現它。

    在Linux上處理文件系統和文件IO訪問仍然令人懊惱,因為它不是基于Unicode的。Open()函數和文件系統的層都有危險的平臺指定的缺省選項。例如,如果我從一臺de_AT的機器SSH到一臺en_US機器,那么Python對文件系統和文件操作就喜歡回退到ASCII編碼上。

    我注意到通常Python3上對文本操作最可靠的同時也在2.x正常工作的方法是僅僅以二進制模式打開文件,然后顯式地進行解碼。另外,你也可以使用2.x上的codec.open或者io.open函數,以及Python 3上內置的帶有編碼參數的Open函數。

    標準庫里的URL不能用Unicode正確地表示,這使得一些URL在3.x里不能被正確的處理。

    由于更改了語法,所以追溯對象產生的異常需要輔助函數。通常來說這非常罕見,而且很容易處理。下面是由于更改了語法所遇到的情形之一,在這種情況下,你將不得不把代碼移到exec塊里。

     if PY2:
     exec('def reraise(tp, value, tb):
     raise tp, value, tb')
     else:
     def reraise(tp, value, tb):
     raise value.with_traceback(tb)

    如果你有部分代碼依賴于不同的語法的話,那么通常來說前面的exec技巧是非常有用的。不過現在由于exec本身就有不同的語法,所以你不能用它來執行任何命名空間上的操作。下面給出的代碼段不會有大問題,因為把compile用做嵌入函數的eval可運行在兩個版本上。另外你可以通過exec本身啟動一個exec函數。

    exec_ = lambda s, *a: eval(compile(s, '', 'exec'), *a)

    如果你在Python C API上書寫了C模塊,那么自殺吧。從我知道那刻起到仙子仍然沒有工具可處理這個問題,而且許多東西都已經更改了。借此機會放棄你構造模塊所使用的這種方法,然后在cffi或者ctypes上重新書寫模塊。如果這種方法還不行的話,因為你有點頑固,那么只有接受這樣的痛苦。也許試著在C預處理器上書寫某些令人討厭的事可以使移植更容易些。

    使用Tox來進行本地測試。能夠立刻在所有Python版本上運行你的測試是非常有益的,這將為你找到許多問題。

    展望

    統一2.x和3.x的基本編碼庫現在確實可以開始了。移植的大量時間仍然將花費在試圖解決有關Unicode以及與其他可能已經更改了自身API的模塊交互時API是如何操作上。無論如何,如果你打算考慮移植庫的話,那么請不要觸碰2.5以下的版本、3.0-3.2版本,這樣的話將不會對版本造成太大的傷害。

    聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

    文檔

    把項目從Python2.x移植到Python3.x的經驗總結

    把項目從Python2.x移植到Python3.x的經驗總結:經歷移植jinja2到python3的痛苦之后,我把項目暫時放一放,因為我怕打破python3的兼容。我的做法是只用一個python2的代碼庫,然后在安裝的時候用2to3工具翻譯成python3。不幸的是哪怕一點點的改動都會打破迭代開發。如果你選對了python的版本,你可以專
    推薦度:
    標簽: 項目 python 移植
    • 熱門焦點

    最新推薦

    猜你喜歡

    熱門推薦

    專題
    Top
    主站蜘蛛池模板: 久久精品无码一区二区app| 亚洲综合无码精品一区二区三区| 久久夜色精品国产www| 国产麻豆一精品一AV一免费| 精品露脸国产偷人在视频| 精品成人免费自拍视频| 日韩精品少妇无码受不了| 国内精品99亚洲免费高清| 四虎影视884a精品国产四虎| 国产精品白浆在线观看免费| 一本色道久久综合亚洲精品| 精品日韩欧美国产| 99精品欧美一区二区三区| 999成人精品视频在线| 久久香蕉国产线看观看精品yw| 日本一区二区三区精品国产| 97久久精品午夜一区二区| 国产精品 猎奇 另类视频| laowang在线精品视频| 婷婷五月深深久久精品| 欧美精品整片300页| 国产精品videossex白浆| 日本精品久久久久中文字幕| 国产精品成人观看视频免费| 国产精品免费AV片在线观看| 人妻少妇精品视频一区二区三区 | 无码AV动漫精品一区二区免费| 国产精品久久久久久久午夜片 | 亚洲欧美日韩国产精品一区二区| 国产精品手机在线观看你懂的| 热99re久久国超精品首页| 精品亚洲综合在线第一区| 国产成人精品免费视频动漫| 国产精品无码专区| 精品无码一区在线观看| 久久er99热精品一区二区| 久久精品国产亚洲AV大全| 欧美精品v欧洲精品| 精品视频无码一区二区三区| 久久99精品久久只有精品| 婷婷国产成人精品视频|