<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # Chapter 15 案例研究:將`chardet`移植到Python 3 > " Words, words. They’re all we have to go on. " > — [Rosencrantz and Guildenstern are Dead](http://www.imdb.com/title/tt0100519/quotes) ## 概述 未知的或者不正確的字符編碼是因特網上無效數據(gibberish text)的頭號起因。在[第3章](strings.html),我們討論過字符編碼的歷史,還有Unicode的產生,“一個能處理所有情況的大塊頭。”如果在網絡上不再存在亂碼這回事,我會愛上她的…因為所有的編輯系統(authoring system)保存有精確的編碼信息,所有的傳輸協議都支持Unicode,所有處理文本的系統在執行編碼間轉換的時候都可以保持高度精確。 我也會喜歡pony。 Unicode pony。 Unipony也行。 這一章我會處理編碼的自動檢測。 ## 什么是字符編碼自動檢測? 它是指當面對一串不知道編碼信息的字節流的時候,嘗試著確定一種編碼方式以使我們能夠讀懂其中的文本內容。它就像我們沒有解密鑰匙的時候,嘗試破解出編碼。 ### 那不是不可能的嗎? 通常來說,是的,不可能。但是,有一些編碼方式為特定的語言做了優化,而語言并非隨機存在的。有一些字符序列在某種語言中總是會出現,而其他一些序列對該語言來說則毫無意義。一個熟練掌握英語的人翻開報紙,然后發現“txzqJv 2!dasd0a QqdKjvz”這樣一些序列,他會馬上意識到這不是英語(即使它完全由英語中的字母組成)。通過研究許多具有“代表性(typical)”的文本,計算機算法可以模擬人的這種對語言的感知,并且對一段文本的語言做出啟發性的猜測。 換句話說就是,檢測編碼信息就是檢測語言的類型,并輔之一些額外信息,比如每種語言通常會使用哪些編碼方式。 ### 這樣的算法存在嗎? 結果證明,是的,它存在。所有主流的瀏覽器都有字符編碼自動檢測的功能,因為因特網上總是充斥著大量缺乏編碼信息的頁面。[Mozilla Firefox包含有一個自動檢測字符編碼的庫](http://lxr.mozilla.org/seamonkey/source/extensions/universalchardet/src/base/),它是開源的。[我將它導入到了Python 2](http://chardet.feedparser.org/),并且取綽號為`chardet`模塊。這一章中,我會帶領你一步一步地將`chardet`模塊從Python 2移植到Python 3。 ## 介紹`chardet`模塊 在開始代碼移植之前,如果我們能理解代碼是如何工作的這將非常有幫助!以下是一個簡明地關于`chardet`模塊代碼結構的手冊。`chardet`庫太大,不可能都放在這兒,但是你可以[從`chardet.feedparser.org`下載它](http://chardet.feedparser.org/download/)。 編碼檢測就是語言檢測。 `universaldetector.py`是檢測算法的主入口點,它包含一個類,即`UniversalDetector`。(可能你會認為入口點是`chardet/__init__.py`中的`detect`函數,但是它只是一個便捷的包裝方法,它會創建`UniversalDetector`對象,調用對象的方法,然后返回其結果。) `UniversalDetector`共處理5類編碼方式: 1. 包含字節順序標記(BOM)的UTF-n。它包括UTF-8,大尾端和小尾端的UTF-16,還有所有4字節順序的UTF-32的變體。 2. 轉義編碼,它們與7字節的ASCII編碼兼容,非ASCII編碼的字符會以一個轉義序列打頭。比如:ISO-2022-JP(日文)和HZ-GB-2312(中文). 3. 多字節編碼,在這種編碼方式中,每個字符使用可變長度的字節表示。比如:Big5(中文),SHIFT_JIS(日文),EUC-KR(韓文)和缺少BOM標記的UTF-8。 4. 單字節編碼,這種編碼方式中,每個字符使用一個字節編碼。例如:KOI8-R(俄語),windows-1255(希伯來語)和TIS-620(泰國語)。 5. windows-1252,它主要被根本不知道字符編碼的中層管理人員(middle manager)在Microsoft Windows上使用。 ### 有BOM標記的UTF-n 如果文本以BOM標記打頭,我們可以合理地假設它使用了UTF-8,UTF-16或者UTF-32編碼。(BOM會告訴我們是其中哪一種,這就是它的功能。)這個過程在`UniversalDetector`中完成,并且不需要深入處理,會非常快地返回其結果。 ### 轉義編碼 如果文本包含有可識別的能指示出某種轉義編碼的轉義序列,`UniversalDetector`會創建一個`EscCharSetProber`對象(在`escprober.py`中定義),然后以該文本調用它。 `EscCharSetProber`會根據HZ-GB-2312,ISO-2022-CN,ISO-2022-JP,和ISO-2022-KR(在`escsm.py`中定義)來創建一系列的狀態機(state machine)。`EscCharSetProber`將文本一次一個字節地輸入到這些狀態機中。如果某一個狀態機最終唯一地確定了字符編碼,`EscCharSetProber`迅速地將該有效結果返回給`UniversalDetector`,然后`UniversalDetector`將其返回給調用者。如果某一狀態機進入了非法序列,它會被放棄,然后使用其他的狀態機繼續處理。 ### 多字節編碼 假設沒有BOM標記,`UniversalDetector`會檢測該文本是否包含任何高位字符(high-bit character)。如果有的話,它會創建一系列的“探測器(probers)”,檢測這段廣西是否使用多字節編碼,單字節編碼,或者作為最后的手段,是否為`windows-1252`編碼。 這里的多字節編碼探測器,即`MBCSGroupProber`(在`mbcsgroupprober.py`中定義),實際上是一個管理一組其他探測器的shell,它用來處理每種多字節編碼:Big5,GB2312,EUC-TW,EUC-KR,EUC-JP,SHIFT_JIS和UTF-8。`MBCSGroupProber`將文本作為每一個特定編碼探測器的輸入,并且檢測其結果。如果某個探測器報告說它發現了一個非法的字節序列,那么該探測器則會被放棄,不再進一步處理(因此,換句話說就是,任何對`UniversalDetector`.`feed()`的子調用都會忽略那個探測器)。如果某一探測器報告說它有足夠理由確信找到了正確的字符編碼,那么`MBCSGroupProber`會將這個好消息傳遞給`UniversalDetector`,然后`UniversalDetector`將結果返回給調用者。 大多數的多字節編碼探測器從類`MultiByteCharSetProber`(定義在`mbcharsetprober.py`中)繼承而來,簡單地掛上合適的狀態機和分布分析器(distribution analyzer),然后讓`MultiByteCharSetProber`做剩余的工作。`MultiByteCharSetProber`將文本作為特定編碼狀態機的輸入,每次一個字節,尋找能夠指示出一個確定的正面或者負面結果的字節序列。同時,`MultiByteCharSetProber`會將文本作為特定編碼分布分析機的輸入。 分布分析機(在`chardistribution.py`中定義)使用特定語言的模型,此模型中的字符在該語言被使用得最頻繁。一旦`MultiByteCharSetProber`把足夠的文本給了分布分析機,它會根據其中頻繁使用字符的數目,字符的總數和特定語言的分配比(distribution ratio),來計算置信度(confidence rating)。如果置信度足夠高,`MultiByteCharSetProber`會將結果返回給`MBCSGroupProber`,然后由`MBCSGroupProber`返回給`UniversalDetector`,最后`UniversalDetector`將其返回給調用者。 對于日語來說檢測會更加困難。單字符的分布分析并不總能區別出`EUC-JP`和`SHIFT_JIS`,所以`SJISProber`(在`sjisprober.py`中定義)也使用雙字符的分布分析。`SJISContextAnalysis`和`EUCJPContextAnalysis`(都定義在`jpcntx.py`中,并且都從類`JapaneseContextAnalysis`中繼承)檢測文本中的平假名音節字符(Hiragana syllabary characher)的出現次數。一旦處理了足夠量的文本,它會返回一個置信度給`SJISProber`,`SJISProber`檢查兩個分析器的結果,然后將置信度高的那個返回給`MBCSGroupProber`。 ### 單字節編碼 說正經的,我的Unicode pony哪兒去了? 單字節編碼的探測器,即`SBCSGroupProber`(定義在`sbcsgroupprober.py`中),也是一個管理一組其他探測器的shell,它會嘗試單字節編碼和語言的每種組合:`windows-1251`,`KOI8-R`,`ISO-8859-5`,`MacCyrillic`,`IBM855`,and `IBM866`(俄語);`ISO-8859-7`和`windows-1253`(希臘語);`ISO-8859-5`和`windows-1251`(保加利亞語);`ISO-8859-2`和`windows-1250`(匈牙利語);`TIS-620`(泰國語);`windows-1255`和`ISO-8859-8`(希伯來語)。 `SBCSGroupProber`將文本輸入給這些特定編碼+語言的探測器,然后檢測它們的返回值。這些探測器的實現為某一個類,即`SingleByteCharSetProber`(在`sbcharsetprober.py`中定義),它使用語言模型(language model)作為其參數。語言模型定義了典型文本中不同雙字符序列出現的頻度。`SingleByteCharSetProber`處理文本,統計出使用得最頻繁的雙字符序列。一旦處理了足夠多的文本,它會根據頻繁使用的序列的數目,字符總數和特定語言的分布系數來計算其置信度。 希伯來語被作為一種特殊的情況處理。如果在雙字符分布分析中,文本被認定為是希伯來語,`HebrewProber`(在`hebrewprober.py`中定義)會嘗試將其從Visual Hebrew(源文本一行一行地被“反向”存儲,然后一字不差地顯示出來,這樣就能從右到左的閱讀)和Logical Hebrew(源文本以閱讀的順序保存,在客戶端從右到左進行渲染)區別開來。因為有一些字符在兩種希伯來語中會以不同的方式編碼,這依賴于它們是出現在單詞的中間或者末尾,這樣我們可以合理的猜測源文本的存儲方向,然后返回合適的編碼方式(`windows-1255`對應Logical Hebrew,或者`ISO-8859-8`對應Visual Hebrew)。 ### `windows-1252` 如果`UniversalDetector`在文本中檢測到一個高位字符,但是其他的多字節編碼探測器或者單字節編碼探測器都沒有返回一個足夠可靠的結果,它就會創建一個`Latin1Prober`對象(在`latin1prober.py`中定義),嘗試從中檢測以`windows-1252`方式編碼的英文文本。這種檢測存在其固有的不可靠性,因為在不同的編碼中,英文字符通常使用了相同的編碼方式。唯一一種區別能出`windows-1252`的方法是通過檢測常用的符號,比如[彎引號(smart quotes)](http://en.wikipedia.org/wiki/Smart_quotes),撇號(curly apostrophes),版權符號(copyright symbol)等這一類的符號。如果可能`Latin1Prober`會自動降低其置信度以使其他更精確的探測器檢出結果。 ## 運行`2to3` 我們將要開始移植`chardet`模塊到Python 3了。Python 3自帶了一個叫做`2to3`的實用腳本,它使用Python 2的源代碼作為輸入,然后盡其可能地將其轉換到Python 3的規范。某些情況下這很簡單?—?一個被重命名或者被移動到其他模塊中的函數?—?但是有些情況下,這個過程會變得非常復雜。想要了解所有它_能_做的事情,請參考附錄,[使用`2to3`將代碼移植到Python 3](porting-code-to-python-3-with-2to3.html)。接下來,我們會首先運行一次`2to3`,將它作用在`chardet`模塊上,但是就如你即將看到的,在該自動化工具完成它的魔法表演后,仍然存在許多工作需要我們來收拾。 `chardet`包被分割為一些不同的文件,它們都放在同一個目錄下。`2to3`能夠立即處理多個文件:只需要將目錄名作為命令行參數傳遞給`2to3`,然后它會輪流處理每個文件。 ``` C:\home\chardet> python c:\Python30\Tools\Scripts\2to3.py -w chardet\ RefactoringTool: Skipping implicit fixer: buffer RefactoringTool: Skipping implicit fixer: idioms RefactoringTool: Skipping implicit fixer: set_literal RefactoringTool: Skipping implicit fixer: ws_comma --- chardet\__init__.py (original) +++ chardet\__init__.py (refactored) @@ -18,7 +18,7 @@ __version__ = "1.0.1" def detect(aBuf): ~~- import universaldetector~~ <ins>+ from . import universaldetector</ins> u = universaldetector.UniversalDetector() u.reset() u.feed(aBuf) --- chardet\big5prober.py (original) +++ chardet\big5prober.py (refactored) @@ -25,10 +25,10 @@ # 02110-1301 USA ######################### END LICENSE BLOCK ######################### ~~-from mbcharsetprober import MultiByteCharSetProber~~ ~~-from codingstatemachine import CodingStateMachine~~ ~~-from chardistribution import Big5DistributionAnalysis~~ ~~-from mbcssm import Big5SMModel~~ <ins>+from .mbcharsetprober import MultiByteCharSetProber</ins> <ins>+from .codingstatemachine import CodingStateMachine</ins> <ins>+from .chardistribution import Big5DistributionAnalysis</ins> <ins>+from .mbcssm import Big5SMModel</ins> class Big5Prober(MultiByteCharSetProber): def __init__(self): --- chardet\chardistribution.py (original) +++ chardet\chardistribution.py (refactored) @@ -25,12 +25,12 @@ # 02110-1301 USA ######################### END LICENSE BLOCK ######################### ~~-import constants~~ ~~-from euctwfreq import EUCTWCharToFreqOrder, EUCTW_TABLE_SIZE, EUCTW_TYPICAL_DISTRIBUTION_RATIO~~ ~~-from euckrfreq import EUCKRCharToFreqOrder, EUCKR_TABLE_SIZE, EUCKR_TYPICAL_DISTRIBUTION_RATIO~~ ~~-from gb2312freq import GB2312CharToFreqOrder, GB2312_TABLE_SIZE, GB2312_TYPICAL_DISTRIBUTION_RATIO~~ ~~-from big5freq import Big5CharToFreqOrder, BIG5_TABLE_SIZE, BIG5_TYPICAL_DISTRIBUTION_RATIO~~ ~~-from jisfreq import JISCharToFreqOrder, JIS_TABLE_SIZE, JIS_TYPICAL_DISTRIBUTION_RATIO~~ <ins>+from . import constants</ins> <ins>+from .euctwfreq import EUCTWCharToFreqOrder, EUCTW_TABLE_SIZE, EUCTW_TYPICAL_DISTRIBUTION_RATIO</ins> <ins>+from .euckrfreq import EUCKRCharToFreqOrder, EUCKR_TABLE_SIZE, EUCKR_TYPICAL_DISTRIBUTION_RATIO</ins> <ins>+from .gb2312freq import GB2312CharToFreqOrder, GB2312_TABLE_SIZE, GB2312_TYPICAL_DISTRIBUTION_RATIO</ins> <ins>+from .big5freq import Big5CharToFreqOrder, BIG5_TABLE_SIZE, BIG5_TYPICAL_DISTRIBUTION_RATIO</ins> <ins>+from .jisfreq import JISCharToFreqOrder, JIS_TABLE_SIZE, JIS_TYPICAL_DISTRIBUTION_RATIO</ins> ENOUGH_DATA_THRESHOLD = 1024 SURE_YES = 0.99 . . <mark>. (it goes on like this for a while)</mark> . . RefactoringTool: Files that were modified: RefactoringTool: chardet\__init__.py RefactoringTool: chardet\big5prober.py RefactoringTool: chardet\chardistribution.py RefactoringTool: chardet\charsetgroupprober.py RefactoringTool: chardet\codingstatemachine.py RefactoringTool: chardet\constants.py RefactoringTool: chardet\escprober.py RefactoringTool: chardet\escsm.py RefactoringTool: chardet\eucjpprober.py RefactoringTool: chardet\euckrprober.py RefactoringTool: chardet\euctwprober.py RefactoringTool: chardet\gb2312prober.py RefactoringTool: chardet\hebrewprober.py RefactoringTool: chardet\jpcntx.py RefactoringTool: chardet\langbulgarianmodel.py RefactoringTool: chardet\langcyrillicmodel.py RefactoringTool: chardet\langgreekmodel.py RefactoringTool: chardet\langhebrewmodel.py RefactoringTool: chardet\langhungarianmodel.py RefactoringTool: chardet\langthaimodel.py RefactoringTool: chardet\latin1prober.py RefactoringTool: chardet\mbcharsetprober.py RefactoringTool: chardet\mbcsgroupprober.py RefactoringTool: chardet\mbcssm.py RefactoringTool: chardet\sbcharsetprober.py RefactoringTool: chardet\sbcsgroupprober.py RefactoringTool: chardet\sjisprober.py RefactoringTool: chardet\universaldetector.py RefactoringTool: chardet\utf8prober.py ``` 現在我們對測試工具?—?`test.py`?—?應用`2to3`腳本。 ``` C:\home\chardet> python c:\Python30\Tools\Scripts\2to3.py -w test.py RefactoringTool: Skipping implicit fixer: buffer RefactoringTool: Skipping implicit fixer: idioms RefactoringTool: Skipping implicit fixer: set_literal RefactoringTool: Skipping implicit fixer: ws_comma --- test.py (original) +++ test.py (refactored) @@ -4,7 +4,7 @@ count = 0 u = UniversalDetector() for f in glob.glob(sys.argv[1]): ~~- print f.ljust(60),~~ <ins>+ print(f.ljust(60), end=' ')</ins> u.reset() for line in file(f, 'rb'): u.feed(line) @@ -12,8 +12,8 @@ u.close() result = u.result if result['encoding']: ~~- print result['encoding'], 'with confidence', result['confidence']~~ <ins>+ print(result['encoding'], 'with confidence', result['confidence'])</ins> else: ~~- print '******** no result'~~ <ins>+ print('******** no result')</ins> count += 1 ~~-print count, 'tests'~~ <ins>+print(count, 'tests')</ins> RefactoringTool: Files that were modified: RefactoringTool: test.py ``` 看吧,還不算太難。只是轉換了一些impor和print語句。說到這兒,那些import語句_原來_到底存在什么問題呢?為了回答這個問題,你需要知道`chardet`是如果被分割到多個文件的。 ## 題外話,關于多文件模塊 `chardet`是一個_多文件模塊_。我也可以將所有的代碼都放在一個文件里(并命名為`chardet.py`),但是我沒有。我創建了一個目錄(叫做`chardet`),然后我在那個目錄里創建了一個`__init__.py`文件。_如果Python看到目錄里有一個`__init__.py`文件,它會假設該目錄里的所有文件都是同一個模塊的某部分。_模塊名為目錄的名字。目錄中的文件可以引用目錄中的其他文件,甚至子目錄中的也行。(再講一分鐘這個。)但是整個文件集合被作為一個單獨的模塊呈現給其他的Python代碼?—?就好像所有的函數和類都在一個`.py`文件里。 在`__init__.py`中到底有些什么?什么也沒有。一切。界于兩者之間。`__init__.py`文件不需要定義任何東西;它確實可以是一個空文件。或者也可以使用它來定義我們的主入口函數。或者把我們所有的函數都放進去。或者其他函數都放,單單不放某一個函數… > ?包含有`__init__.py`文件的目錄總是被看作一個多文件的模塊。沒有`__init__.py`文件的目錄中,那些`.py`文件是不相關的。 我們來看看它實際上是怎樣工作的。 ``` >>> import chardet ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '__version__', 'detect'] <module 'chardet' from 'C:\Python31\lib\site-packages\chardet\__init__.py'> ``` 1. 除了常見的類屬性,在`chardet`模塊中只多了一個`detect()`函數。 2. 這是我們發覺`chardet`模塊不只是一個文件的第一個線索:“module”被當作文件`chardet/`目錄中的`__init__.py`文件列出來。 我們再來瞟一眼`__init__.py`文件。 ``` u = universaldetector.UniversalDetector() u.reset() u.feed(aBuf) u.close() return u.result ``` 1. `__init__.py`文件定義了`detect()`函數,它是`chardet`庫的主入口點。 2. 但是`detect()`函數沒有任何實際的代碼!事實上,它所做的事情只是導入了`universaldetector`模塊然后開始調用它。但是`universaldetector`定義在哪兒? 答案就在那行古怪的`import`語句中: ``` from . import universaldetector ``` 翻譯成中文就是,“導入`universaldetector`模塊;它跟我在同一目錄,”這里的我即指文件`chardet/__init__.py`。這是一種提供給多文件模塊中文件之間互相引用的方法,不需要擔心它會與已經安裝的[搜索路徑](your-first-python-program.html#importsearchpath)中的模塊發生命名沖突。該條`import`語句_只會_在`chardet/`目錄中查找`universaldetector`模塊。 這兩條概念?—?`__init__.py`和相對導入?—?意味著我們可以將模塊分割為任意多個塊。`chardet`模塊由36個`.py`文件組成?—?36!但我們所需要做的只是使用`chardet/__init__.py`文件中定義的某個函數。還有一件事情沒有告訴你,`detect()`使用了相對導入來引用了`chardet/universaldetector.py`中定義的一個類,然后這個類又使用了相對導入引用了其他5個文件的內容,它們都在`chardet/`目錄中。 > ?如果你發現自己正在用Python寫一個大型的庫(或者更可能的情況是,當你意識到你的小模塊已經變得很大的時候),最好花一些時間將它重構為一個多文件模塊。這是Python所擅長的許多事情之一,那就利用一下這個優勢吧。 ## 修復`2to3`腳本所不能做的 ### `False` is invalid syntax 你確實有測試樣例,對吧? 現在開始真正的測試:使用測試集運行測試工具。由于測試集被設計成可以覆蓋所有可能的代碼路徑,它是用來測試移植后的代碼,保證bug不會埋伏在某個地方的一種不錯的辦法。 ``` C:\home\chardet> python test.py tests\*\* Traceback (most recent call last): File "test.py", line 1, in <module> from chardet.universaldetector import UniversalDetector File "C:\home\chardet\chardet\universaldetector.py", line 51 self.done = constants.False ^ SyntaxError: invalid syntax ``` 唔,一個小麻煩。在Python 3中,`False`是一個保留字,所以不能把它用作變量名。我們來看一看`constants.py`來確定這是在哪兒定義的。以下是`constants.py`在執行`2to3`腳本之前原來的版本。 ``` import __builtin__ if not hasattr(__builtin__, 'False'): False = 0 True = 1 else: False = __builtin__.False True = __builtin__.True ``` 這一段代碼用來允許庫在低版本的Python 2中運行,在Python 2.3以前,Python沒有內置的`bool`類型。這段代碼檢測內置的`True`和`False`常量是否缺失,如果必要的話則定義它們。 但是,Python 3總是有`bool`類型的,所以整個這片代碼都沒有必要。最簡單的方法是將所有的`constants.True`和`constants.False`都分別替換成`True`和`False`,然后將這段死代碼從`constants.py`中移除。 所以`universaldetector.py`中的以下行: ``` self.done = constants.False ``` 變成了 ``` self.done = False ``` 啊哈,是不是很有滿足感?代碼不僅更短了,而且更具可讀性。 ### No module named `constants` 是時候再運行一次`test.py`了,看看它能走多遠。 ``` C:\home\chardet> python test.py tests\*\* Traceback (most recent call last): File "test.py", line 1, in <module> from chardet.universaldetector import UniversalDetector File "C:\home\chardet\chardet\universaldetector.py", line 29, in <module> import constants, sys ImportError: No module named constants ``` 說什么了?不存在叫做`constants`的模塊?可是當然有`constants`這個模塊了。它就在`chardet/constants.py`中。 還記得什么時候`2to3`腳本會修復所有那些導入語句嗎?這個包內有許多的相對導入?—?即,[在同一個庫中,導入其他模塊的模塊](#multifile-modules)?—?但是在_Python 3中相對導入的邏輯已經變了_。在Python 2中,我們只需要`import constants`,然后它就會首先在`chardet/`目錄中查找。在Python 3中,[所有的導入語句默認使用絕對路徑](http://www.python.org/dev/peps/pep-0328/)。如果想要在Python 3中使用相對導入,你需要顯式地說明: ``` from . import constants ``` 但是。`2to3`腳本難道不是要自動修復這些的嗎?好吧,它確實這樣做了,但是該條導入語句在同一行組合了兩種不同的導入類型:庫內部對`constants`的相對導入,還有就是對`sys`模塊的絕對導入,`sys`模塊已經預裝在了Python的標準庫里。在Python 2里,我們可以將其組合到一條導入語句中。在Python 3中,我們不能這樣做,并且`2to3`腳本也不是那樣聰明,它不能把這條導入語句分成兩條。 解決的辦法是把這條導入語句手動的分成兩條。所以這條二合一的導入語句: ``` import constants, sys ``` 需要變成兩條分享的導入語句: ``` from . import constants import sys ``` 在`chardet`庫中還分散著許多這類問題的變體。某些地方它是“`import constants, sys`”;其他一些地方則是“`import constants, re`”。修改的方法是一樣的:手工地將其分割為兩條語句,一條為相對導入準備,另一條用于絕對導入。 前進! ### Name `'file'` is not defined open()代替了原來的file()。PapayaWhip則替代了原來的black 再來一次,運行`test.py`來執行我們的測試樣例… ``` C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml Traceback (most recent call last): File "test.py", line 9, in <module> for line in file(f, 'rb'): NameError: name 'file' is not defined ``` 這一條也出乎我的意外,因為在記憶中我一直都在使用這種風格的代碼。在Python 2里,全局的`file()`函數是`open()`函數的一個別名,`open()`函數是[打開文件用于讀取](files.html#reading)的標準方法。在Python 3中,全局的`file()`函數不再存在了,但是`open()`還保留著。 這樣的話,最簡單的解決辦法就是將`file()`調用替換為對`open()`的調用: ``` for line in open(f, 'rb'): ``` 這即是我關于這個問題想要說的。 ### Can’t use a string pattern on a bytes-like object 現在事情開始變得有趣了。對于“有趣,”我的意思是“跟地獄一樣讓人迷茫。” ``` C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 98, in feed if self._highBitDetector.search(aBuf): TypeError: can't use a string pattern on a bytes-like object ``` 我們先來看看`self._highBitDetector`是什么,然后再來調試這個錯誤。它被定義在`UniversalDetector`類的`__init__`方法中。 ``` class UniversalDetector: def __init__(self): self._highBitDetector = re.compile(r'[\x80-\xFF]') ``` 這段代碼預編譯一條正則表達式,它用來查找在128–255 (0x80–0xFF)范圍內的非ASCII字符。等一下,這似乎不太準確;我需要對更精確的術語來描述它。這個模式用來在128-255范圍內查找非ASCII的_bytes_。 問題就出在這兒了。 在Python 2中,字符串是一個字節數組,它的字符編碼信息被分開記錄著。如果想要Python 2跟蹤字符編碼,你得使用Unicode編碼的字符串(`u''`)。但是在Python 3中,字符串永遠都是Python 2中所謂的Unicode編碼的字符串?—?即,Unicode字符數組(可能存在可變長字節)。由于這條正則表達式是使用字符串模式定義的,所以它只能用來搜索字符串?—?再強調一次,字符數組。但是我們所搜索的并非字符串,它是一個字節數組。看一看traceback,該錯誤發生在`universaldetector.py`: ``` def feed(self, aBuf): . . . if self._mInputState == ePureAscii: if self._highBitDetector.search(aBuf): ``` `aBuf`是什么?讓我們原路回到調用`UniversalDetector.feed()`的地方。有一處地方調用了它,是測試工具,`test.py`。 ``` u = UniversalDetector() . . . for line in open(f, 'rb'): u.feed(line) ``` 非字符數組,而是一個字節數組。 在此處我們找到了答案:`UniversalDetector.feed()`方法中,`aBuf`是從磁盤文件中讀到的一行。仔細看一看用來打開文件的參數:`'rb'`。`'r'`是用來讀取的;OK,沒什么了不起的,我們在讀取文件。啊,但是[`'b'`是用以讀取“二進制”數據的。](files.html#binary)如果沒有標記`'b'`,`for`循環會一行一行地讀取文件,然后將其轉換為一個字符串?—?Unicode編碼的字符數組?—?根據系統默認的編碼方式。但是使用`'b'`標記后,`for`循環一行一行地讀取文件,然后將其按原樣存儲為字節數組。該字節數組被傳遞給了 `UniversalDetector.feed()`方法,最后給了預編譯好的正則表達式,`self._highBitDetector`,用來搜索高位…字符。但是沒有字符;有的只是字節。蒼天哪。 我們需要該正則表達式搜索的并不是字符數組,而是一個字節數組。 只要我們認識到了這一點,解決辦法就有了。使用字符串定義的正則表達式可以搜索字符串。使用字節數組定義的正則表達式可以搜索字節數組。我們只需要改變用來定義正則表達式的參數的類型為字節數組,就可以定義一個字節數組模式。(還有另外一個該問題的實例,在下一行。) ``` class UniversalDetector: def __init__(self): ~~- self._highBitDetector = re.compile(r'[\x80-\xFF]')~~ ~~- self._escDetector = re.compile(r'(\033|~{)')~~ <ins>+ self._highBitDetector = re.compile(b'[\x80-\xFF]')</ins> <ins>+ self._escDetector = re.compile(b'(\033|~{)')</ins> self._mEscCharSetProber = None self._mCharSetProbers = [] self.reset() ``` 在整個代碼庫內搜索對`re`模塊的使用發現了另外兩個該類型問題的實例,出現在`charsetprober.py`文件中。再次,以上代碼將正則表達式定義為字符串,但是卻將它們作用在`aBuf`上,而`aBuf`是一個字節數組。解決方案還是一樣的:將正則表達式模式定義為字節數組。 ``` class CharSetProber: . . . def filter_high_bit_only(self, aBuf): ~~- aBuf = re.sub(r'([\x00-\x7F])+', ' ', aBuf)~~ <ins>+ aBuf = re.sub(b'([\x00-\x7F])+', b' ', aBuf)</ins> return aBuf def filter_without_english_letters(self, aBuf): ~~- aBuf = re.sub(r'([A-Za-z])+', ' ', aBuf)~~ <ins>+ aBuf = re.sub(b'([A-Za-z])+', b' ', aBuf)</ins> return aBuf ``` ### Can't convert `'bytes'` object to `str` implicitly 奇怪,越來越不尋常了… ``` C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 100, in feed elif (self._mInputState == ePureAscii) and self._escDetector.search(self._mLastChar + aBuf): TypeError: Can't convert 'bytes' object to str implicitly ``` 在此存在一個Python解釋器與代碼風格之間的不協調。`TypeError`可以出現在那一行的任意地方,但是traceback不能明確定地指出錯誤的位置。可能是第一個或者第二個條件語句(conditional),對traceback來說,它們是一樣的。為了縮小調試的范圍,我們需要把這條代碼分割成兩行,像這樣: ``` elif (self._mInputState == ePureAscii) and \ self._escDetector.search(self._mLastChar + aBuf): ``` 然后再運行測試工具: ``` C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 101, in feed self._escDetector.search(self._mLastChar + aBuf): TypeError: Can't convert 'bytes' object to str implicitly ``` 啊哈!錯誤不在第一個條件語句上(`self._mInputState == ePureAscii`),是第二個的問題。但是,是什么引發了`TypeError`錯誤呢?也許你會想`search()`方法需要另外一種類型的參數,但是那樣的話,就不會產生當前這種traceback了。Python函數可以使用任何類型參數;只要傳遞了正確數目的參數,函數就可以執行。如果我們給函數傳遞了類型不匹配的參數,代碼可能就會_崩潰_,但是這樣一來,traceback就會指向函數內部的某一代碼塊了。但是當前得到的traceback告訴我們,錯誤就出現在開始調用`search()`函數那兒。所以錯誤肯定就出在`+`操作符上,該操作用于構建最終會傳遞給`search()`方法的參數。 從[前一次調試](#cantuseastringpattern)的過程中,我們已經知道`aBuf`是一個字節數組。那么`self._mLastChar`又是什么呢?它是一個在`reset()`中定義的實例變量,而`reset()`方法剛好就是被`__init__()`調用的。 ``` class UniversalDetector: def __init__(self): self._highBitDetector = re.compile(b'[\x80-\xFF]') self._escDetector = re.compile(b'(\033|~{)') self._mEscCharSetProber = None self._mCharSetProbers = [] <mark>self.reset()</mark> def reset(self): self.result = {'encoding': None, 'confidence': 0.0} self.done = False self._mStart = True self._mGotData = False self._mInputState = ePureAscii <mark>self._mLastChar = ''</mark> ``` 現在我們找到問題的癥結所在了。你發現了嗎?`self._mLastChar`是一個字符串,而`aBuf`是一個字節數組。而我們不允許對字符串和字節數組做連接操作?—?即使是空串也不行。 那么,`self._mLastChar`到底是什么呢?在`feed()`方法中,在traceback報告的位置以下幾行就是了。 ``` if self._mInputState == ePureAscii: if self._highBitDetector.search(aBuf): self._mInputState = eHighbyte elif (self._mInputState == ePureAscii) and \ self._escDetector.search(self._mLastChar + aBuf): self._mInputState = eEscAscii <mark>self._mLastChar = aBuf[-1]</mark> ``` `feed()`方法被一次一次地調用,每次都傳遞給它幾個字節。該方法處理好它收到的字節(以`aBuf`傳遞進去的),然后將最后一個字節保存在`self._mLastChar`中,以便下次調用時還會用到。(在多字節編碼中,`feed()`在調用的時候可能只收到了某個字符的一半,然后下次調用時另一半才被傳到。)但是因為`aBuf`已經變成了一個字節數組,所以`self._mLastChar`也需要與其匹配。可以這樣做: ``` def reset(self): . . . ~~- self._mLastChar = ''~~ <ins>+ self._mLastChar = b''</ins> ``` 在代碼庫中搜索“`mLastChar`”,`mbcharsetprober.py`中也發現一個相似的問題,與之前不同的是,它記錄的是最后_2_個字符。`MultiByteCharSetProber`類使用一個單字符列表來記錄末尾的兩個字符。在Python 3中,這需要使用一個整數列表,因為實際上它記錄的并不是是字符,而是字節對象。(字節對象即范圍在`0-255`內的整數。) ``` class MultiByteCharSetProber(CharSetProber): def __init__(self): CharSetProber.__init__(self) self._mDistributionAnalyzer = None self._mCodingSM = None ~~- self._mLastChar = ['\x00', '\x00']~~ <ins>+ self._mLastChar = [0, 0]</ins> def reset(self): CharSetProber.reset(self) if self._mCodingSM: self._mCodingSM.reset() if self._mDistributionAnalyzer: self._mDistributionAnalyzer.reset() ~~- self._mLastChar = ['\x00', '\x00']~~ <ins>+ self._mLastChar = [0, 0]</ins> ``` ### Unsupported operand type(s) for +: `'int'` and `'bytes'` 有好消息,也有壞消息。好消息是我們一直在前進著… ``` C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 101, in feed self._escDetector.search(self._mLastChar + aBuf): TypeError: unsupported operand type(s) for +: 'int' and 'bytes' ``` …壞消息是,我們好像一直都在原地踏步。 但我們確實一直在取得進展!真的!即使traceback在相同的地方再次出現,這一次的錯誤畢竟與上次不同。前進!那么,這次又是什么錯誤呢?上一次我們確認過了,這一行代碼不應該會再做連接`int`型和字節數組(`bytes`)的操作。事實上,我們剛剛花了相當長一段時間來[保證`self._mLastChar`是一個字節數組](#cantconvertbytesobject)。它怎么會變成`int`呢? 答案不在上幾行代碼中,而在以下幾行。 ``` if self._mInputState == ePureAscii: if self._highBitDetector.search(aBuf): self._mInputState = eHighbyte elif (self._mInputState == ePureAscii) and \ self._escDetector.search(self._mLastChar + aBuf): self._mInputState = eEscAscii <mark>self._mLastChar = aBuf[-1]</mark> ``` 字符串中的元素仍然是字符串,字節數組中的元素則為整數。 該錯誤沒有發生在`feed()`方法第一次被調用的時候;而是在_第二次_調用的過程中,在`self._mLastChar`被賦值為`aBuf`末尾的那個字節之后。好吧,這又會有什么問題呢?因為獲取字節數組中的單個元素會產生一個整數,而不是字節數組。它們之間的區別,請看以下在交互式shell中的操作: ``` >>> len(aBuf) 3 >>> mLastChar = aBuf[-1] 191 <class 'int'> Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'bytes' >>> mLastChar b'\xbf' b'\xbf\xef\xbb\xbf' ``` 1. 定義一個長度為3的字節數組。 2. 字節數組的最后一個元素為191。 3. 它是一個整數。 4. 連接整數和字節數組的操作是不允許的。我們重復了在`universaldetector.py`中發現的那個錯誤。 5. 啊,這就是解決辦法了。使用[列表分片](native-datatypes.html#slicinglists)從數組的最后一個元素中創建一個新的字節數組,而不是直接獲取這個元素。即,從最后一個元素開始切割,直到到達數組的末尾。當前`mLastChar`是一個長度為1的字節數組。 6. 連接長度分別為1和3的字節數組,則會返回一個新的長度為4的字節數組。 所以,為了保證`universaldetector.py`中的`feed()`方法不管被調用多少次都能夠正常運行,我們需要[將`self._mLastChar`實例化為一個長度為0的字節數組](#cantconvertbytesobject),并且保證它一直是一個字節數組。 ``` self._escDetector.search(self._mLastChar + aBuf): self._mInputState = eEscAscii ~~- self._mLastChar = aBuf[-1]~~ <ins>+ self._mLastChar = aBuf[-1:]</ins> ``` ### `ord()` expected string of length 1, but `int` found 困了嗎?就要完成了… ``` C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml ascii with confidence 1.0 tests\Big5\0804.blogspot.com.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 116, in feed if prober.feed(aBuf) == constants.eFoundIt: File "C:\home\chardet\chardet\charsetgroupprober.py", line 60, in feed st = prober.feed(aBuf) File "C:\home\chardet\chardet\utf8prober.py", line 53, in feed codingState = self._mCodingSM.next_state(c) File "C:\home\chardet\chardet\codingstatemachine.py", line 43, in next_state byteCls = self._mModel['classTable'][ord(c)] TypeError: ord() expected string of length 1, but int found ``` OK,因為`c`是`int`類型的,但是`ord()`需要一個長度為1的字符串。就是這樣了。`c`在哪兒定義的? ``` # codingstatemachine.py def next_state(self, c): # for each byte we get its class # if it is first byte, we also get byte length byteCls = self._mModel['classTable'][ord(c)] ``` 不是這兒; 此處`c`只是被傳遞給了`next_state()`函數。我們再上一級看看。 ``` # utf8prober.py def feed(self, aBuf): for c in aBuf: codingState = self._mCodingSM.next_state(c) ``` 看到了嗎?在Python 2中,`aBuf`是一個字符串,所以`c`就是一個長度為1的字符串。(那就是我們通過遍歷字符串所得到的?—?所有的字符,一次一個。)因為現在`aBuf`是一個字節數組,所以`c`變成了`int`類型的,而不再是長度為1的字符串。也就是說,沒有必要再調用`ord()`函數了,因為`c`已經是`int`了! 這樣修改: ``` def next_state(self, c): # for each byte we get its class # if it is first byte, we also get byte length ~~- byteCls = self._mModel['classTable'][ord(c)]~~ <ins>+ byteCls = self._mModel['classTable'][c]</ins> ``` 在代碼庫中搜索“`ord(c)`”后,發現`sbcharsetprober.py`中也有相似的問題… ``` # sbcharsetprober.py def feed(self, aBuf): if not self._mModel['keepEnglishLetter']: aBuf = self.filter_without_english_letters(aBuf) aLen = len(aBuf) if not aLen: return self.get_state() for c in aBuf: <mark>order = self._mModel['charToOrderMap'][ord(c)]</mark> ``` …還有`latin1prober.py`… ``` # latin1prober.py def feed(self, aBuf): aBuf = self.filter_with_english_letters(aBuf) for c in aBuf: <mark>charClass = Latin1_CharToClass[ord(c)]</mark> ``` `c`在`aBuf`中遍歷,這就意味著它是一個整數,而非字符串。解決方案是相同的:把`ord(c)`就替換成`c`。 ``` # sbcharsetprober.py def feed(self, aBuf): if not self._mModel['keepEnglishLetter']: aBuf = self.filter_without_english_letters(aBuf) aLen = len(aBuf) if not aLen: return self.get_state() for c in aBuf: ~~- order = self._mModel['charToOrderMap'][ord(c)]~~ <ins>+ order = self._mModel['charToOrderMap'][c]</ins> # latin1prober.py def feed(self, aBuf): aBuf = self.filter_with_english_letters(aBuf) for c in aBuf: ~~- charClass = Latin1_CharToClass[ord(c)]~~ <ins>+ charClass = Latin1_CharToClass[c]</ins> ``` ### Unorderable types: `int()` &gt;= `str()` 繼續我們的路吧。 ``` C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml ascii with confidence 1.0 tests\Big5\0804.blogspot.com.xml Traceback (most recent call last): File "test.py", line 10, in <module> u.feed(line) File "C:\home\chardet\chardet\universaldetector.py", line 116, in feed if prober.feed(aBuf) == constants.eFoundIt: File "C:\home\chardet\chardet\charsetgroupprober.py", line 60, in feed st = prober.feed(aBuf) File "C:\home\chardet\chardet\sjisprober.py", line 68, in feed self._mContextAnalyzer.feed(self._mLastChar[2 - charLen :], charLen) File "C:\home\chardet\chardet\jpcntx.py", line 145, in feed order, charLen = self.get_order(aBuf[i:i+2]) File "C:\home\chardet\chardet\jpcntx.py", line 176, in get_order if ((aStr[0] >= '\x81') and (aStr[0] <= '\x9F')) or \ TypeError: unorderable types: int() >= str() ``` 這都是些什么?“Unorderable types”?字節數組與字符串之間的差異引起的問題再一次出現了。看一看以下代碼: ``` class SJISContextAnalysis(JapaneseContextAnalysis): def get_order(self, aStr): if not aStr: return -1, 1 # find out current char's byte length <mark>if ((aStr[0] >= '\x81') and (aStr[0] <= '\x9F')) or \</mark> ((aStr[0] >= '\xE0') and (aStr[0] <= '\xFC')): charLen = 2 else: charLen = 1 ``` `aStr`從何而來?再深入棧內看一看: ``` def feed(self, aBuf, aLen): . . . i = self._mNeedToSkipCharNum while i < aLen: <mark>order, charLen = self.get_order(aBuf[i:i+2])</mark> ``` 看,是`aBuf`,我們的老戰友。從我們在這一章中所遇到的問題你也可以猜到了問題的關鍵了,因為`aBuf`是一個字節數組。此處`feed()`方法并不是整個地將它傳遞出去;而是先對它執行分片操作。就如你在[這章前面](#unsupportedoperandtypeforplus)看到的,對字節數組執行分片操作的返回值仍然為字節數組,所以傳遞給`get_order()`方法的`aStr`仍然是字節數組。 那么以下代碼是怎樣處理`aStr`的呢?它將該字節第一個元素與長度為1的字符串進行比較操作。在Python 2,這是可以的,因為`aStr`和`aBuf`都是字符串,所以`aStr[0]`也是字符串,并且我們允許比較兩個字符串的是否相等。但是在Python 3中,`aStr`和`aBuf`都是字節數組,而`aStr[0]`就成了一個整數,沒有執行顯式地強制轉換的話,是不能對整數和字符串執行相等性比較的。 在當前情況下,沒有必要添加強制轉換,這會讓代碼變得更加復雜。`aStr[0]`產生一個整數;而我們所比較的對象都是常量(constant)。那就把長度為1的字符串換成整數吧。我們也順便把`aStr`換成`aBuf`吧,因為`aStr`本來也不是一個字符串。 ``` class SJISContextAnalysis(JapaneseContextAnalysis): ~~- def get_order(self, aStr):~~ ~~- if not aStr: return -1, 1~~ <ins>+ def get_order(self, aBuf):</ins> <ins>+ if not aBuf: return -1, 1</ins> # find out current char's byte length ~~- if ((aStr[0] >= '\x81') and (aStr[0] <= '\x9F')) or \~~ ~~- ((aBuf[0] >= '\xE0') and (aBuf[0] <= '\xFC')):~~ <ins>+ if ((aBuf[0] >= 0x81) and (aBuf[0] <= 0x9F)) or \</ins> <ins>+ ((aBuf[0] >= 0xE0) and (aBuf[0] <= 0xFC)):</ins> charLen = 2 else: charLen = 1 # return its order if it is hiragana ~~- if len(aStr) > 1:~~ ~~- if (aStr[0] == '\202') and \~~ ~~- (aStr[1] >= '\x9F') and \~~ ~~- (aStr[1] <= '\xF1'):~~ ~~- return ord(aStr[1]) - 0x9F, charLen~~ <ins>+ if len(aBuf) > 1:</ins> <ins>+ if (aBuf[0] == 0x202) and \</ins> <ins>+ (aBuf[1] >= 0x9F) and \</ins> <ins>+ (aBuf[1] <= 0xF1):</ins> <ins>+ return aBuf[1] - 0x9F, charLen</ins> return -1, charLen class EUCJPContextAnalysis(JapaneseContextAnalysis): ~~- def get_order(self, aStr):~~ ~~- if not aStr: return -1, 1~~ <ins>+ def get_order(self, aBuf):</ins> <ins>+ if not aBuf: return -1, 1</ins> # find out current char's byte length ~~- if (aStr[0] == '\x8E') or \~~ ~~- ((aStr[0] >= '\xA1') and (aStr[0] <= '\xFE')):~~ <ins>+ if (aBuf[0] == 0x8E) or \</ins> <ins>+ ((aBuf[0] >= 0xA1) and (aBuf[0] <= 0xFE)):</ins> charLen = 2 ~~- elif aStr[0] == '\x8F':~~ <ins>+ elif aBuf[0] == 0x8F:</ins> charLen = 3 else: charLen = 1 # return its order if it is hiragana ~~- if len(aStr) > 1:~~ ~~- if (aStr[0] == '\xA4') and \~~ ~~- (aStr[1] >= '\xA1') and \~~ ~~- (aStr[1] <= '\xF3'):~~ ~~- return ord(aStr[1]) - 0xA1, charLen~~ <ins>+ if len(aBuf) > 1:</ins> <ins>+ if (aBuf[0] == 0xA4) and \</ins> <ins>+ (aBuf[1] >= 0xA1) and \</ins> <ins>+ (aBuf[1] <= 0xF3):</ins> <ins>+ return aBuf[1] - 0xA1, charLen</ins> return -1, charLen ``` 在代碼庫中查找`ord()`函數,我們在`chardistribution.py`中也發現了同樣的問題(更確切地說,在以下這些類中,`EUCTWDistributionAnalysis`,`EUCKRDistributionAnalysis`,`GB2312DistributionAnalysis`,`Big5DistributionAnalysis`,`SJISDistributionAnalysis`和`EUCJPDistributionAnalysis`)。對于它們存在的問題,解決辦法與我們對`jpcntx.py`中的類`EUCJPContextAnalysis`和`SJISContextAnalysis`的做法相似。 ### Global name `'reduce'` is not defined 再次陷入中斷… ``` C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml ascii with confidence 1.0 tests\Big5\0804.blogspot.com.xml Traceback (most recent call last): File "test.py", line 12, in <module> u.close() File "C:\home\chardet\chardet\universaldetector.py", line 141, in close proberConfidence = prober.get_confidence() File "C:\home\chardet\chardet\latin1prober.py", line 126, in get_confidence total = reduce(operator.add, self._mFreqCounter) NameError: global name 'reduce' is not defined ``` 根據官方手冊:[What’s New In Python 3.0](http://docs.python.org/3.0/whatsnew/3.0.html#builtins),函數`reduce()`已經從全局名字空間中移出,放到了`functools`模塊中。引用手冊中的內容:“如果需要,請使用`functools.reduce()`,99%的情況下,顯式的`for`循環使代碼更有可讀性。”你可以從Guido van Rossum的一篇日志中看到關于這項決策的更多細節:[The fate of reduce() in Python 3000](http://www.artima.com/weblogs/viewpost.jsp?thread=98196)。 ``` def get_confidence(self): if self.get_state() == constants.eNotMe: return 0.01 <mark>total = reduce(operator.add, self._mFreqCounter)</mark> ``` `reduce()`函數使用兩個參數?—?一個函數,一個列表(更嚴格地說,可迭代的對象就行了)?—?然后將函數增量式地作用在列表的每個元素上。換句話說,這是一種良好而高效的用于綜合(add up)列表所有元素并返回其結果的方法。 這種強大的技術使用如此頻繁,所以Python就添加了一個全局的`sum()`函數。 ``` def get_confidence(self): if self.get_state() == constants.eNotMe: return 0.01 ~~- total = reduce(operator.add, self._mFreqCounter)~~ <ins>+ total = sum(self._mFreqCounter)</ins> ``` 由于我們不再使用`operator`模塊,所以可以在文件最上方移除那條`import`語句。 ``` from .charsetprober import CharSetProber from . import constants ~~- import operator~~ ``` 可以開始測試了吧?(快要吐血的樣子…) ``` C:\home\chardet> python test.py tests\*\* tests\ascii\howto.diveintomark.org.xml ascii with confidence 1.0 tests\Big5\0804.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\blog.worren.net.xml Big5 with confidence 0.99 tests\Big5\carbonxiv.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\catshadow.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\coolloud.org.tw.xml Big5 with confidence 0.99 tests\Big5\digitalwall.com.xml Big5 with confidence 0.99 tests\Big5\ebao.us.xml Big5 with confidence 0.99 tests\Big5\fudesign.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\kafkatseng.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\ke207.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\leavesth.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\letterlego.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\linyijen.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\marilynwu.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\myblog.pchome.com.tw.xml Big5 with confidence 0.99 tests\Big5\oui-design.com.xml Big5 with confidence 0.99 tests\Big5\sanwenji.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\sinica.edu.tw.xml Big5 with confidence 0.99 tests\Big5\sylvia1976.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\tlkkuo.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\tw.blog.xubg.com.xml Big5 with confidence 0.99 tests\Big5\unoriginalblog.com.xml Big5 with confidence 0.99 tests\Big5\upsaid.com.xml Big5 with confidence 0.99 tests\Big5\willythecop.blogspot.com.xml Big5 with confidence 0.99 tests\Big5\ytc.blogspot.com.xml Big5 with confidence 0.99 tests\EUC-JP\aivy.co.jp.xml EUC-JP with confidence 0.99 tests\EUC-JP\akaname.main.jp.xml EUC-JP with confidence 0.99 tests\EUC-JP\arclamp.jp.xml EUC-JP with confidence 0.99 . . . 316 tests ``` 天哪,伙計,她真的歡快地跑起來了!_[/me does a little dance](http://www.hampsterdance.com/)_ ## 總結 我們學到了什么? 1. 嘗試大批量地把代碼從Python 2移植到Python 3上是一件讓人頭疼的工作。沒有捷徑。它確實很困難。 2. [自動化的`2to3`腳本](porting-code-to-python-3-with-2to3.html)確實有用,但是它只能做一些簡單的輔助工作?—?函數重命名,模塊重命名,語法修改等。之前,它被認為是一項會讓人印象深刻的大工程,但是最后,實際上它只是一個能智能地執行查找替換機器人。 3. 在移植`chardet`庫的時候遇到的頭號問題就是:字符串和字節對象之間的差異。在我們這個情況中,這種問題比較明顯,因為整個`chardet`庫就是一直在執行從字節流到字符串的轉換。但是“字節流”出現的方式會遠超出你的想象。以“二進制”模式讀取文件?我們會獲得字節流。獲取一份web頁面?調用web API?這也會返回字節流。 4. _你_需要徹底地了解所面對的程序。如果那段程序是自己寫自然非常好,但是至少,我們需要夠理解所有晦澀難懂的細節。因為bug可能埋伏在任何地方。 5. 測試樣例是必要的。沒有它們的話不要嘗試著移植代碼。我自信移植后的`chardet`模塊能在Python 3中工作的_唯一_理由是,我一開始就使用了測試集合來檢驗所有主要的代碼路徑。如果你還沒有任何測試集,在移植代碼之前自己寫一些吧。如果你的測試集合太小,那么請寫全。如果測試集夠了,那么,我們就又可以開始歷險了。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看