Python--Cmd窗口運行Python時提示Fatal Python error: Py_Initialize: can't initialize sys standard streams Loo

源地址鏈接:php

http://www.tuicool.com/articles/ryuaUzehtml

 

最近,我在把一個 Python 2 的視頻下載工具 youku-lixian 改寫成 Python 3,並添加了本身須要的 YouTube 支持。python

在 Linux 下,事情進行得很順利:全部的東西都用 UTF-8 進行編碼。Python 3 裏的 str 類型從 2.x 版本的 ASCII 字符串變成了 Unicode 字符串;我移除了原來代碼裏關於本地編碼類型的判斷處理部分。程序從抓取的頁面上解析出視頻標題部分的 Unicode 字符串,直接 print ()顯示到標準輸出,一切看起來很和諧。git

假定我抓取的這個視頻標題是中文,叫作「你好,世界」。衆所周知,得益於 Python 良好的 Unicode 支持,輸出它只須要簡單的一句:程序員

print ('你好,世界')github

在 天殺的 Windows 7 下測試這個程序時,麻煩就出現了。若是你想知道我爲何這麼說,請繼續看下去。windows

我所不瞭解的 Windows瀏覽器

去年從學校拿到這臺 Dell 筆記本時,Windows 7 天然是預裝在上面的。ruby

系統語言已經設置成了英語。很快,我對瑞典語鍵盤的佈局感到極其不適應:它的標點符號位置與英語鍵盤佈局有很大區別,分號、冒號、單引號雙引號、斜槓反斜槓這些程序員司空見慣的符號,和美式英語鍵盤徹底不一樣。因而,我把鍵盤佈局換回了習慣的英語鍵盤,順便把控制面板的「區域」選項也一律從瑞典換到了英語/美國。bash

在很長一段時間裏,除了界面是英文之外,它看起來和以往用的中文系統沒什麼區別:有默認的中文字體,輸入法能夠添加中文的。我平時用它作的,只有:上網,掛迅雷,拿 IE 登網銀,玩 Mirror's Edge,幾件事而已。

文件系統是 Unicode 編碼的,Web 瀏覽器是支持 Unicode 的,偶爾用的文本編輯器也是一概設置成 UTF-8 的。並且咱們知道,從 Windows 2000 起,Windows 的內碼實現是使用 UTF-16LE 的。幾乎讓人快要忘了還有代碼頁這麼一回事。

但是,若是要在英文 Windows 系統的命令提示符裏執行這個簡單的輸出 Unicode 文本的程序:

#!/usr/bin/env python
# -*- coding: utf-8 -*- if __name__ == '__main__': print ('你好,世界')

Python 就會跳出來一段錯誤:

File "c:\Python32\lib\encodings\cp437.py", line 19, in encode return codecs.charmap_encode (input,self.errors,encoding_map)[0] UnicodeEncodeError: 'charmap' codec can't encode characters in position 0-1: character maps to <undefined>

難道 Python 3 不是支持 Unicode 的嗎?難道它不是跨平臺的嗎?

第一個問題,基本上是對的,Python 3 確實支持 Unicode,這種支持體如今它把全部的 str 字符串都做爲 Unicode 處理這件事情上。

第二個問題,不徹底,跨平臺的可移植性是有條件的。Python 自己是支持 Unicode,可是若是趕上了非 Unicode 的古董環境,那就一點辦法也沒有。

什麼叫「非 Unicode 的古董環境」呢……不,我說的不是 DOS。這個東西,居然就是 Windows 上的 cmd.exe ,每一個人或多或少都用過的命令行環境。

cmd.exe ,從 MinGW 到 Python,基本上每一個 Windows 下須要接觸命令行的開發人員都躲不過去的東西,微軟怎麼就不能把它作好些?窗口大小不能爲所欲爲改也就算了,不能全屏顯示也就算了,字體大小屏幕緩衝設置各類限制也就算了,鼠標拖拽不方便也就算了,命令行補全補不全也就算了,你好歹能把默認編碼改爲用 Unicode 吧?一個破窗口從二十年前的 3.x 時代沿用到今天的 Windows 7,從依賴 DOS 的command.com 到獨立的 cmd.exe ,尼瑪這麼多年了,也沒見功能上有什麼實質的改進,是否是在微軟眼裏全部的程序員都在拿個白花花的 IDE「作你的 code」、不須要命令行了?

(在 Windows 已經徹底使用 UTF-16 做爲內碼實現的今天, cmd.exe 仍然在使用系統默認的代碼頁,我所能想到的惟一理由就是爲了保持和之前的 non-Unicode 程序兼容——不過這理由也太弱了吧)

微軟有功夫把 Windows 8 的界面作得花裏胡哨,不過看樣子他們是壓根不打算把cmd.exe 這個東西作得更好用些。不繼續噴下去了,說處理問題的通過:

前面 Python 的錯誤信息裏提到了個文件 cp437.py 。既然是 cp437 什麼的,那就必定是 Python 在試圖把 Unicode 字符串轉換成用於輸出的 437 代碼頁(英語/美國)時出了錯。

爲何 Python 要把一個好端端的 Unicode 字符串轉換成 cp437 呢?這很容易想通,由於程序是在 cmd.exe 這個終端環境下執行的。在個人英文系統上,它的活動代碼頁是 437(英語/美國)。從代碼中的 Unicode 字符串到輸出 cp437 的這一步轉換,是由 Python 解釋器來實現的,因此會由 Python 拋出一個錯誤,而不是直接在控制檯輸出一堆亂碼。

首先想到的解決方案,天然是改變當前 cmd.exe 的活動代碼頁到 UTF-8 Unicode:

chcp.com 65001

不幸的是,這致使 Python 解釋器直接崩潰了:

Fatal Python error: Py_Initialize: can't initialize sys standard streams LookupError: unknown encoding: cp65001 This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information. LookupError: unknown encoding: cp65001

搜了一下才發現,Python 3.2 目前並不支持 Windows 上面的 cp65001。話說 65001 代碼頁不就是 UTF-8 嘛(囧囧囧囧囧)

與其說是不支持,不如說是 bug 更合適些。由於執行以後 Windows 就跳出一個警告框說「 python.exe 已經中止響應」了……

因而,試着改變代碼頁到 GBK:

chcp.com 936

結果倒是:

Invalid code page

Windows 聲稱這是一個無效的代碼頁。爲何?

編碼是什麼

好了,暫且忘記 cmd.exe 諸如此類使人不愉快的東西,在 IDLE 上試一試。

我不知道有多少 Linux 程序員寫 Python 的時候會用到 IDLE。對於這些習慣了終端+文本編輯器的用戶來講,IDLE 看起來是個可有可無的附屬品,也許它的定位只是用來幫助初學者入門的一個開發環境?

不過,容易被人們忽略的一點是:IDLE 自己是個跨平臺的環境,這意味着它能夠無條件支持 Unicode(只要系統上有相應的字體),用它來解釋執行程序沒必要受制於特定終端環境的拘束。這一點在 Windows 上很重要,由於 cmd.exe 這玩意實在是太差勁了,因此估計不少人在 Windows 下交互執行 Python 的時候仍是會選擇 IDLE 的。

進入 IDLE。咱們可能要關心一下這個 Windows 系統下面的默認編碼方式是什麼,Python 3 裏面有兩個函數:

>>> sys.stdout.encoding; locale.getpreferredencoding ()
'cp1252' 'cp1252'

第一個sys.stdout.encoding是指標準輸出的編碼,第二個locale.getpreferredencoding則是系統本地化設置的編碼。二者是有區別的。如今咱們看到,它們在當前環境下是相同的,都是默認的cp1252,也就是傳說中的「ANSI」代碼頁。

恩,咱們已經知道 IDLE 是一個徹底跨平臺的環境,因此在 IDLE 上輸出 Unicode 字符能夠獲得和 Linux 環境下一樣和諧的結果:

>>> print ('你好,世界')你好,世界

順便看看「你好,世界」的 UTF-8 編碼和 GBK 是什麼,若是強制用其餘編碼方式來解碼又會獲得怎樣的結果(後面也許會用到)。能夠看到,5 個全角字符在 UTF-8 編碼下是 15 個字節,每一個字符佔 3 bytes;在 GBK 編碼下是 10 個字節,每一個字符佔 2 bytes。

雖然沒有什麼實際的意義,不過仍是能夠注意到:UTF-8 編碼的字符是沒法用 GBK 解碼的,哪怕是亂碼有時候也不行,由於可能會出現奇數字節長度,這在 GBK 下不合法;反之 GBK 編碼字符亦沒法用 UTF-8 解碼,由於有無效字符值的存在。

藉助 IDLE 看到了「你好,世界」各類編碼的詳細狀況。如今咱們能夠回到 cmd.exe裏面看一看下面這段程序的運行結果了:

#!/usr/bin/env python # -*- coding: utf-8 -*- import sys, locale if __name__ == '__main__': print(sys.stdout.encoding, locale.getpreferredencoding ()) try: print('你好,世界') except Exception as err: print(str (err))

首先經過 chcp 確認, cmd.exe 的當前活動代碼頁是 437(英語/美國),而非 IDLE 裏的 1252(ANSI)。這是因爲個人 Windows 裏對 non-Unicode 程序的區域設置是「英語/美國」的緣故。

程序運行的結果是:

cp437 cp1252
'charmap' codec can't encode characters in position 0-4:  character maps to <undefined>

能夠看到 sys.stdout.encoding 實際上就是當前環境下活動代碼頁的值。locale.getpreferredencoding () 沒變,仍然是系統默認的 cp1252。

以後拋出的異常是在咱們預料之中的,正如此前同樣,Python 嘗試把 Unicode 字符串轉換成 cmd 終端下的 cp437 代碼頁編碼。而中文字符原本就是沒有對應的 cp437 編碼的,因此 Python 報錯。

Google 一下 'charmap' codec can't encode characters in position 0-4: character maps to <undefined> 這個錯誤。在 Stack Overflow 上,有人提到了解決的方法:設置一個叫作 PYTHONIOENCODING 的環境變量。

PYTHONIOENCODING 環境變量

所謂的 PYTHONIOENCODING ,既能夠做爲環境變量存在,也能夠做爲 Python 的命令行參數傳遞。它用於指定 Python 程序標準輸入輸出(stdin/stdout/stderr)的編碼。(注意這個編碼不是指源代碼的編碼,和 Python 程序開頭常見的 # -*- coding: utf-8 -*- 是兩碼事)

在沒有這個環境變量時

如前面所述,Python 會試圖把內部 Unicode 編碼的字符串轉化成當前執行程序的終端環境下所使用的編碼方式( sys.stdout.encoding )後輸出。對於當前代碼頁 437 的 cmd.exe 來講,把只含有英文數字的字符串轉成 cp437 編碼沒有任何問題;可是一旦趕上了中文字符,英語/美國的 437 代碼頁裏必然是找不到對應的編碼的,因而 Python 就會報錯。

若是當前代碼頁設成 65001,Python 3.2 會崩潰,這是自己實現上的 問題 。在最新的 Python 3.3 beta 中已經 增長了對 cp65001 的支持 。

在設置了這個環境變量時

經過

set PYTHONIOENCODING=utf-8

或(PowerShell 下)

$env:PYTHONIOENCODING = "utf-8"

PYTHONIOENCODING 指定的編碼方式會覆蓋原來的 sys.stdout.encoding 。若是將 PYTHONIOENCODING 設置爲 utf-8,那麼 Python 在輸出 Unicode 字符串的時候就會以 UTF-8 輸出,至關於什麼也不轉換。

再次執行該 Python 程序,這一次 Python 再也不嘗試自動轉換 Unicode 的中文字符到 cp437 中的對應字符,程序成功運行, sys.stdout.encoding 變成了 utf-8,字符串輸出則是亂碼:

utf-8 cp1252你好,世界

這與咱們以前在 IDLE 中將 UTF-8 編碼的文本強制用 cp437 解碼獲得的結果是徹底相同的:

>>> print(bytes ('你好,世界', 'utf-8') .decode ('cp437')) Σ╜áσÑ╜∩╝îΣ╕ûτòî

Python 直接把 UTF-8 編碼的字符串輸出到了 cp437 代碼頁的終端,至關於強制用 cp437 來解碼 UTF-8 文本,產生了無心義的亂碼。

用文本編輯器寫一個內容是「你好,世界」的文件,以 UTF-8 編碼保存。在 cmd.exe下經過 type 顯示,結果和上面是相同的。

cmd.exe 和 PowerShell ISE 的微妙之處對比

在當前區域設置(英語/美國)下,二者執行 chcp.com 顯示的當前活動代碼頁都是 437。

只有 cmd 下 Python 的 sys.stdout.encoding 默認是 cp437(與活動代碼頁相同);PowerShell ISE 下 sys.stdout.encoding 則是 cp1252(ANSI)。

locale.getpreferredencoding 永遠是系統自己默認的 cp1252,這是一個系統全局值。

cmd 沒法輸入中文,不能正確顯示文件系統中的中文文件名;PowerShell ISE 可以輸入中文,能顯示中文文件名。

在缺乏 936 代碼頁的狀況下,二者都不可以經過執行腳本或 type 文件內容正確顯示中文字符(不管是 GBK 仍是 UTF-8),會產生亂碼。

爲何 Windows 會缺乏 GBK 代碼頁?

回到最初的那個問題上來,爲何執行 chcp.com 936 不能切換到 GBK 代碼頁?爲何 cmd.exe 和 PowerShell 裏不能正常顯示中文?

這個問題讓我百思不得其解。花了幾個小時找到了緣由,簡而言之:由於 Windows 的「區域和語言」設置不對。

「Language for non-Unicode programs」這個選項不是簡體中文,因此就不能用 GBK,手動 chcp.com 也會告訴你該代碼頁無效。因此必需要在控制面板裏設置成簡體中文,重啓後才能生效。

好吧,問題來了,爲何這裏只能單選?若是我既想使用 936(GBK)編碼的應用程序,又想使用 932(日語)編碼的應用程序,難道每次都要在這裏改完後再重啓嗎?爲何他們不能給一個詳細的代碼頁列表讓用戶多選、須要時能夠動態加載?

Windows 設計的齷齪之處就在這裏。若是你不去設置 system locale 爲中文並重啓,全部 non-Unicode 程序裏的中文字符集都是不會出現的,只能顯示成一個方框,好比cmd.exe 裏:

還有 Vim 裏( set fileencodings=utf-8,gbk ),GBK 編碼的文本和 UTF-8 編碼的文本都同樣沒法顯示。(按理說 Vim 應該不能算 non-Unicode 程序吧……誰知道呢?!)

改過"Language for non-Unicode programs」爲中文而且重啓系統以後,Vim 當即顯示正常:

再進 cmd.exe ,默認活動代碼頁 936。這段 Python 程序終於也能正確輸出了:

也許 Windows 這種蛋疼的設計是由於考慮到英文用戶通常不會須要多餘的 Unicode 和代碼頁字符集,這麼作能夠節省系統啓動時間?誰知道呢,Windows 用戶不是最喜歡拿所謂的「啓動時間快」做爲衡量系統性能的指標了嗎……

切換到 cp65001(UTF-8 Unicode), PYTHONIOENCODING 設置成 utf-8,按理來講這種方式不該該出問題,可是這輸出怎麼看都不像是正常(以下圖所示)。不想深究到底爲何了,總之 Windows 下面東西的複雜程度以我這種智商是永遠都不可以理解的……

Python 除了標準輸入輸出,還有……

文件名

open ('文件名測試', 'w')

Python 中對文件系統的操做基本上是不受默認編碼影響的,只要sys.getfilesystemencoding () 的結果是 utf-8(現代 Linux)或者 mbcs(現代 Windows NT 系統上)。二者本質上都是 Unicode 編碼。

文件輸入輸出

文件讀寫不屬於標準I/O,所以和環境變量 PYTHONIOENCODING 無關。

for c in ['utf-8', 'gbk']: with open ('test_%s.txt' % c, 'w', encoding=c) as output: try: output.write ('你好,世界\n') except Exception as err: print('\nWriting to file using %s:\n' % c, str (err))

因爲在 open ()中顯式指定了中文編碼方式(encoding='utf-8'encoding='gbk'),輸出「你好,世界」這樣的中文文本在任何平臺上都應該可以獲得正確的結果。

然而對於:

with open ('test_default.txt', 'w') as output: try: output.write ('你好,世界\n') except Exception as err: print('\nWriting to file using default encoding:\n', str (err))

因爲沒有指定編碼方式,Python 會自動使用系統默認的編碼方式來進行輸出。若是系統默認編碼是 cp437 或 cp1252,因爲中文字符在這些代碼頁中顯然不存在對應值,Python 會拋出一個熟悉的錯誤:

File "c:\Python32\lib\encodings\cp437.py", line 19, in encode return codecs.charmap_encode (input,self.errors,encoding_map)[0] UnicodeEncodeError: 'charmap' codec can't encode characters in position 0-1: character maps to <undefined>

固然,當系統默認編碼爲 cp936(GBK)時,不管

output.write ('你好,世界')

仍是

print ('你好,世界')

均可以正常工做。由於「你好,世界」這個 Unicode 字符串是能夠被徹底轉換成 GBK 中的對應編碼的。

一些總結和思考

雖然 Python 3 使用 Unicode 編碼的字符串,可是在跨平臺的程序中依然要取得系統的默認編碼用於後續處理,由於並非全部的終端環境都支持所有的 Unicode 字符集:

if sys.stdout.isatty ()
    default_encoding = sys.stdout.encoding
else default_encoding = locale.getpreferredencoding ()

不管什麼時候,不要爲所欲爲地用 print ()向 stdout 輸出 Unicode 字符串。若是某個要輸出的 Unicode 字符(好比,中文字)在系統默認編碼的字符集(好比,代碼頁 437)上沒有,Python 這時就會拋出一個錯誤。這其實在大部分時候並非咱們想看到的局面,咱們總但願即便有時會輸出一些無心義的亂碼,程序總體上也能正確運行。拿視頻下載工具的例子來講,即便因爲終端的關係有時沒法正確顯示視頻名稱,這問題並不太嚴重,由於程序老是能夠把抓取的視頻內容寫入正確的文件的。

在程序中獲取了系統默認的 default_encoding ,咱們就能夠強制用它來對 Unicode 字符串進行編碼,至少避免了 Python 在自動轉碼過程當中可能會拋出的錯誤——雖然結果可能只是獲得一堆亂碼。另一種處理方式是對於這樣的字符串,咱們決定根本不去輸出它們。

咱們比較願意看到的狀況是:若是程序會輸出且只會輸出中文,而你假想中的 Windows 用戶羣所使用的代碼頁是 936(GBK)——儘管在程序中使用 Unicode 字符串吧,這樣作不會帶來任何問題。

可是,若是不能肯定要處理的 Unicode 文本會限定在哪一個代碼頁字符集的範圍當中:中文?梵文?希伯來文?阿拉伯文?仍是……火星文?這個時候就必須考慮到世界上還有「編碼方式差別」這回事了。固然,最好的解決方式也許是:告訴用戶,去他的代碼頁,去他的什麼 43七、500、93六、1252……這堆詭異的數字,去他的 Bush hid the facts ,扔掉設計上如此糟糕、編碼方式如此混亂和不一致的 Windows,轉投一個讓生活更簡單的操做系統 吧。

相關文章
相關標籤/搜索