python3 rid1.7.4.2 控制台中文乱码_TL;DR - 有关 Python 2 和 Sublime Text 中文 Unicode 编码问题的分析与理解...
TL;DR
問題背景:
相信很多用 Sublime Text 來(lái)寫 Python 2 的同學(xué)都遇到過以下這個(gè)問題(例如這位同學(xué) /t/100435 和這位同學(xué)/t/163012 ):
在 Sublime Text 里用 Cmd (Ctrl) + B 運(yùn)行代碼 print u'中文',想要打印出 unicode 類型的字符串時(shí),會(huì)出現(xiàn)以下報(bào)錯(cuò):
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
傳說中的 Python 2 編碼坑(笑)
而同樣的 print u'中文' 代碼在 Mac 的終端里卻能正常打印出 “中文” 結(jié)果,沒有任何報(bào)錯(cuò)。
雖然在網(wǎng)上能查到多種解決方法,但一直以來(lái)知其然而不知其所以然,不了解為什么那些方法能解決問題的真正原因,也不知道為什么同樣的代碼在終端里就可以運(yùn)行而在 Sublime Text 里就不行了?
因此我研究學(xué)習(xí)了下這個(gè)問題相關(guān)的一些 Python 2 編碼問題,在這里分享下我的理解。
以下屬于新手向,參考了網(wǎng)上多篇文章,如有錯(cuò)誤,望指正。
先說下我的環(huán)境:
Mac OS X
Python 2.7
Sublime Text 3
分析:
Python 在向控制臺(tái) (console) print 的時(shí)候,因?yàn)榭刂婆_(tái)只能看得懂由 bytes(字節(jié)序列)組成的字符串,而 Python 中 "unicode" 對(duì)象存儲(chǔ)的是 code points(碼點(diǎn)),因此 Python 需要將輸出中的 "unicode" 對(duì)象用編碼轉(zhuǎn)換為儲(chǔ)存 bytes(字節(jié)序列)的 "str" 對(duì)象后,才能進(jìn)行輸出。
而在報(bào)錯(cuò)里看到 UnicodeEncodeError, 那就說明 Python 在將 unicode 轉(zhuǎn)換為 str 時(shí)使用了錯(cuò)誤的編碼。而為什么是 'ascii' 編碼呢?那是因?yàn)?Python 2 的默認(rèn)編碼就是 ASCII,可以通過以下命令來(lái)查看 Python 的默認(rèn)編碼:
>>> import sys
>>> print sys.getdefaultencoding()
ascii
所以此時(shí)在 Sublime Text 里運(yùn)行 print u'中文',實(shí)際上等于是運(yùn)行了:
print u'中文'.encode('ascii')
ASCII 編碼無(wú)法對(duì) unicode 的中文進(jìn)行編碼,因此就報(bào)錯(cuò)了。
那為什么同樣的代碼 print u'中文' 在 Mac 的終端里卻能正常輸出中文,難道是因?yàn)榻K端下的 Python 2 的默認(rèn)編碼不是 ASCII?非也,在終端下運(yùn)行 sys.getdefaultencoding() 結(jié)果一樣是 ascii。那同樣是 ascii 為什么會(huì)有不同的結(jié)果?難倒這里 Python 用了另外一個(gè)編碼來(lái)轉(zhuǎn)換?
是的,其實(shí) Python 在 print unicode 時(shí)真正涉及到的是另一組編碼:stdin/stdout/stderr 的編碼,也就是標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出的編碼。可以通過以下命令來(lái)查看,這里是在我的終端下運(yùn)行的結(jié)果:
>>> import sys
>>> print sys.stdin.encoding
UTF-8
>>> print sys.stdout.encoding
UTF-8
>>> print sys.stderr.encoding
UTF-8
在正常情況下,Python 2 在 print unicode 時(shí)用來(lái)轉(zhuǎn)換的編碼并不是 Python 的默認(rèn)編碼 sys.getdefaultencoding(),而是 sys.stdout.encoding 所設(shè)的編碼。
因?yàn)樵谖业慕K端下 Python 的 sys.stdout.encoding 編碼是 UTF-8,所以在終端里運(yùn)行 print u'中文' 時(shí),實(shí)際上是等于運(yùn)行了:
print u'中文'.encode('UTF-8')
編碼正確,運(yùn)行正常,因此沒有報(bào)錯(cuò)。
在類 UNIX 系統(tǒng)下,Python 應(yīng)該是通過環(huán)境變量 LC_CTYPE 來(lái)判斷 stdin/stdout/stderr 的編碼的。因此一般只要將 shell 的 LANG 環(huán)境變量設(shè)置對(duì)為 **_**.UTF-8 后,應(yīng)該就能在終端里直接 print 出 unicode 類型的字符串了,而不需要在 print 時(shí)手動(dòng)加上 .encode('utf-8') 進(jìn)行編碼了。
但在 Sublime Text 里事情就沒那么美好了。在 Sublime Text 里運(yùn)行查看 stdout 編碼的命令,發(fā)現(xiàn):
import sys
print sys.stdout.encoding
-----------------------------"""
None
[Finished in 0.1s]
結(jié)果甚至不是 'ascii' 而是 None。可能是因?yàn)?Sublime Text 的 Build System 是用 subprocess.Popen 來(lái)運(yùn)行 Python 的,導(dǎo)致 Python 無(wú)法判斷出正確的 stdin/stdout/stderr 編碼,于是都變成 None 了。
這種情況也發(fā)生在輸出的目標(biāo)是管道的情況下:
$python -c 'import sys; print sys.stdout.encoding' | tee /tmp/foo.txt
None
那么在這種 sys.stdout.encoding 為 None 情況下的 print unicode 怎么辦呢?答案就是 Python 只能很無(wú)奈地使用 sys.getdefaultencoding() 的默認(rèn)編碼 ascii 來(lái)對(duì) unicode 進(jìn)行轉(zhuǎn)換了。這樣就出現(xiàn)了本文開頭所說的那個(gè) UnicodeEncodeError 問題。
總結(jié)一下 Python 2 向控制臺(tái) print 輸出時(shí)的流程:
Python 啟動(dòng)時(shí),當(dāng)它發(fā)現(xiàn)當(dāng)前的輸出是連接到控制臺(tái)的時(shí)候,它會(huì)根據(jù)一些環(huán)境變量,例如環(huán)境變量 LC_CTYPE,來(lái)設(shè)法判斷出 sys.stdin/stdout/stderr.encoding 編碼值。
當(dāng) Python 無(wú)法判斷出所需的編碼時(shí),它會(huì)將 sys.stdin/stdout/stderr.encoding 的值設(shè)置為 None。
print 時(shí)判斷字符串是否是 unicode 類型。
如果是的話,并且 sys.stdout.encoding 不為 None 時(shí),就使用 sys.stdout.encoding 編碼對(duì) unicode 編碼成 str 后輸出。
如果 sys.stdout.encoding 為 None 的話,就使用 sys.getdefaultencoding() 默認(rèn)編碼來(lái)對(duì) unicode 進(jìn)行轉(zhuǎn)換成 str 后輸出。
if sys.stdout.encoding:
print unicode.encode(sys.stdout.encoding)
else:
print unicode.encode(sys.getdefaultencoding())
解決方法:
解決方法 1:
先說最不正確的解決方法:在文件頭部加上
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
這種方法通過 dirty hack 的方式在 Python 剛啟動(dòng)時(shí)更改了 Python 的默認(rèn)編碼為 utf-8。此后:
>>> print sys.getdefaultencoding()
utf-8
但就本文所討論的問題來(lái)說,這個(gè)方法并不是真正地直接解決了問題。就如上述所說,Python 只是在 sys.stdout.encoding 為 None 時(shí)才會(huì)使用默認(rèn)編碼來(lái)轉(zhuǎn)換需要 print 的 unicode 字符串。那萬(wàn)一在 sys.stdout.encoding 存在,但為 ascii 的情況下呢?這樣即使更改了 Python 的默認(rèn)編碼,同樣還是會(huì)出現(xiàn) UnicodeEncodeError 報(bào)錯(cuò)。所以對(duì)本問題來(lái)說,這個(gè)方法治標(biāo)不治本。
除此之外,很多人都用這個(gè)方法來(lái)解決 Python 2 下遇到的其它各種各樣的編碼問題,在 v2ex 的各種 Python 編碼問題討論帖中也常常能見到有人推薦用這個(gè)方法來(lái)解決問題的。
但實(shí)際上很多大牛都不推薦用這個(gè)方法來(lái)解決 Python 2 的編碼問題,這里引用下 StackOverflow 相關(guān)回答 里的一句話:
the use of sys.setdefaultencoding() has always been discouraged
為什么這個(gè)方法不被推薦呢?我們來(lái)看下 Python 文檔里對(duì)這個(gè) function 是怎么說的:
This function is only intended to be used by the site module implementation and, where needed, by sitecustomize. Once used by the site module, it is removed from the sys module’s namespace.
可以看到這個(gè)方法原本就不是用戶向的方法,并沒有打算讓用戶用這個(gè)方法來(lái)更改 Python 2 的默認(rèn)編碼。
那為什么不建議我們更改 Python 的默認(rèn)編碼呢?
這里引用 Python 核心開發(fā)者、Python Unicode 支持的設(shè)計(jì)者和實(shí)現(xiàn)者: Marc-André Lemburg,他在一個(gè)郵件列表上的回復(fù):
The only supported default encodings in Python are:
Python 2.x: ASCII
Python 3.x: UTF-8
If you change these, you are on your own and strange things will
start to happen. The default encoding does not only affect
the translation between Python and the outside world, but also
all internal conversions between 8-bit strings and Unicode.
Hacks like what's happening in the pango module (setting the
default encoding to 'utf-8' by reloading the site module in
order to get the sys.setdefaultencoding() API back) are just
downright wrong and will cause serious problems since Unicode
objects cache their default encoded representation.
Please don't enable the use of a locale based default encoding.
If all you want to achieve is getting the encodings of
stdout and stdin correctly setup for pipes, you should
instead change the .encoding attribute of those (only).
--
Marc-Andre Lemburg
eGenix.com
從此可見,Python 2 唯一支持的內(nèi)部編碼只有 ASCII,更改其默認(rèn)編碼為其它編碼可能會(huì)導(dǎo)致各種各樣奇怪的問題。在這里他也說了使用 sys.setdefaultencoding() 的方法是徹徹底底的錯(cuò)誤,正確的方法應(yīng)該是更改 stdout 和 stdin 的編碼。
所以這個(gè)方法是最不正確的填坑方法,請(qǐng)大家慎用。
解決方法 2:
然后說說應(yīng)當(dāng)是姿勢(shì)最正確的、也是大家都懂的方法:
在 print 的時(shí)候顯式地用正確的編碼來(lái)對(duì) unicode 類型的字符串進(jìn)行 encode('正確的編碼') 為 str 后, 再進(jìn)行輸出。
而在 print 的時(shí)候,這個(gè)正確的編碼一般就是 sys.stdout.encoding 的值。但也正如上述所說,這個(gè)值并不是一直是可靠的,因此需要根據(jù)所使用的平臺(tái)和控制臺(tái)環(huán)境來(lái)判斷出這個(gè)正確的編碼。
而在 Mac 下這個(gè)正確的編碼一般都是 utf-8,因此若不考慮跨環(huán)境的話,可以無(wú)腦地一直用 encode('utf-8') 和 decode('utf-8') 來(lái)進(jìn)行輸入輸出轉(zhuǎn)換。
在我的經(jīng)驗(yàn)中,這個(gè)策略也是解決 Python 2 其它 unicode 相關(guān)編碼問題的最佳方法。在 PyCon 2012 的一個(gè)演講中(關(guān)于 Python Unicode 問題很好的一個(gè)演講,這里有演講稿的中文翻譯版),對(duì)這個(gè)方法有一個(gè)很形象的比喻:
因?yàn)樵诔绦蛑羞M(jìn)進(jìn)出出的只有存儲(chǔ) bytes(字節(jié)序列)的 str。因此最好的策略是將輸入的 bytes 馬上解碼成 unicode,而在程序內(nèi)部中均使用 unicode,而當(dāng)在進(jìn)行輸出的時(shí)候,盡早將之編碼成 bytes。
也就是要形成一個(gè) Unicode 三明治(如圖), bytes 在外, Unicode 在內(nèi)。在邊界的地方盡早進(jìn)行 decode 和 encode。不要在內(nèi)部混用 str 和 unicode,盡可能地讓程序處理的字符串都為 Unicode。
解決方法 3:
雖然解決方法 2 是最正確的方式,但是有時(shí)候在 Sublime Text 里調(diào)試些小腳本,實(shí)在是懶得再在每個(gè) print 語(yǔ)句后面寫一個(gè)尾巴 .encode('utf-8')。那么有沒有辦法能讓 Sublime Text 像在終端里一樣直接就能 print u'中文' 呢?也就是說能不能解決 sys.stdin/stdout/stderr.encoding 為 None 的情況呢?
答案肯定是有的,一種方法是用類似更改默認(rèn)編碼的方法一樣,用 dirty hack 的方式在 Python 代碼中去顯式地更改 sys.stdin/stdout/stderr.encoding 的值。一樣是不推薦,我也沒嘗試過,在這里就不詳說了。
另一種方法則是通過設(shè)置 PYTHONIOENCODING 環(huán)境變量來(lái)強(qiáng)制要求 Python 設(shè)置 stdin/stdout/stderr 的編碼值為我們想要的,這是一個(gè)相對(duì)比較干凈的解決方法。見文檔:
PYTHONIOENCODING
Overrides the encoding used for stdin/stdout/stderr, in the syntax encodingname:errorhandler. The :errorhandler part is optional and has the same meaning as in str.encode().
New in version 2.6.
在 Mac 下對(duì)全局 GUI 程序設(shè)置環(huán)境變量的方法是:使用 launchctl setenv <, ...> 命令對(duì)所有 launchd 啟動(dòng)的未來(lái)子進(jìn)程設(shè)置環(huán)境變量。
在這里順便科普下,為什么對(duì)所有 launchd 啟動(dòng)的未來(lái)子進(jìn)程設(shè)置環(huán)境變量可以使得對(duì) Mac 下所有 GUI 程序生效。這是因?yàn)?launchd 是 OS X 系統(tǒng)啟動(dòng)后運(yùn)行的第一個(gè)非內(nèi)核進(jìn)程。我們可以在 activity monitor(活動(dòng)監(jiān)視器)里看到,它的 pid 是很帥氣的 1。而之后所有的進(jìn)程都將是它的子進(jìn)程。
另外還可以通過 launchd 在 Mac 下實(shí)現(xiàn)類 crontab 的功能。
launchctl setenv 命令設(shè)置的全局環(huán)境變量會(huì)在電腦重啟后失效,因此就需要通過上面說的 launchd 的開機(jī)啟動(dòng)任務(wù)的功能來(lái)在重啟后再設(shè)置一遍環(huán)境變量,其配置方法可以參考這里。也因?yàn)檫@個(gè)原因,我并沒有使用這個(gè)方法來(lái)設(shè)置 PYTHONIOENCODING 環(huán)境變量。
而 Sublime Text 提供了一個(gè)設(shè)置 Build System 環(huán)境變量的方法,這個(gè)方法各平臺(tái)的 Sublime Text 都適用。
設(shè)置 Sublime Text 的 Python Build System 環(huán)境變量的步驟如下:
將 Sublime Text 默認(rèn)的 Python Build System 的配置文件 Python.sublime-build(找到這個(gè)文件的最好方法是安裝插件 PackageResourceViewer)復(fù)制一份到 Sublime Text 的 /Packages/User 文件夾下(在 Mac 和 Sublime Text 3 下這個(gè)路徑是 ~/Library/Application Support/Sublime Text 3/Packages/User)。
打開編輯新復(fù)制來(lái)的 Python.sublime-build 文件,如下加上一行設(shè)置 PYTHONIOENCODING 環(huán)境變量為 UTF-8 編碼的內(nèi)容,并保存:
{
"shell_cmd": "python -u \"$file\"",
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
+ "env": {"PYTHONIOENCODING": "utf8"},
"selector": "source.python"
}
這樣一來(lái)終于在這么長(zhǎng)的文章后能在 Sublime Text 里直接運(yùn)行 print u'中文',而不用再出現(xiàn)萬(wàn)惡的 UnicodeEncodeError 了。
既然都研究到這了,不妨我們?cè)囋嚢?PYTHONIOENCODING 設(shè)置成其它編碼看看會(huì)出現(xiàn)什么情況,例如設(shè)置成簡(jiǎn)體中文 Windows 的默認(rèn)編碼 cp936:"env": {"PYTHONIOENCODING": "cp936"}
import sys
print sys.stdout.encoding
print u'你好'
----------------------------------"""
cp936
[Decode error - output not utf-8]
[Finished in 0.1s]
[Decode error - output not utf-8],這就是 Sublime Text 在 Windows 下可能會(huì)出現(xiàn)的問題(例如這兩位同學(xué) /t/45391 /t/88428 )。這是因?yàn)?Sublime Text 的 Build System 默認(rèn)是用 utf-8 編碼去解讀運(yùn)行的輸出的,而我們指定了讓 Python 用 cp936 編碼來(lái)生成 str 字符串進(jìn)行輸出,那么就會(huì)出現(xiàn) Sublime Text 無(wú)法識(shí)別輸出的情況了。
同樣在對(duì)終端 export PYTHONIOENCODING=cp936 后,在終端下 print u'你好' 輸出的就會(huì)是 ��� 這樣的亂碼。
解決辦法之一就是同樣在 Python.sublime-build 文件里設(shè)置 "env": {"PYTHONIOENCODING": "utf8"} 來(lái)使得輸出統(tǒng)一為 utf-8。
或者是更改 Sublime Text 的 Build System 所接受的輸出編碼,將其改為一致的 cp936 編碼,同樣也是更改 Python.sublime-build 文件,加入一行:
{
"shell_cmd": "python -u \"$file\"",
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
+ "encoding": "cp936",
"selector": "source.python"
}
那我們?cè)僭囋嚢堰@兩個(gè)設(shè)置同時(shí)都加到 Python.sublime-build 文件里,也就是讓 Python 輸出 utf8 編碼的字符串,而讓 Sublime Text 用 cp936 編碼來(lái)解讀,看看會(huì)發(fā)生什么情況?
{
"shell_cmd": "python -u \"$file\"",
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
+ "env": {"PYTHONIOENCODING": "utf8"},
+ "encoding": "cp936",
"selector": "source.python"
}
print u'你好'
----------------------"""
浣犲ソ
[Finished in 0.1s]
笑,居然不是 [Decode error - output not cp936],而是這么喜感的 “浣犲ソ”!
這是因?yàn)?“你好” 的 utf-8 編碼剛好和 “浣犲ソ” 的 cp936 編碼重合了,都是 '\xe6\xb5\xa3\xe7\x8a\xb2\xe3\x82\xbd',所以使用 cp936 編碼去解讀的 Sublime Text 就認(rèn)為這段字符串就是 “浣犲ソ” 而顯示了出來(lái)。
>>> print repr('浣犲ソ') # cp936 編碼
'\xe6\xb5\xa3\xe7\x8a\xb2\xe3\x82\xbd'
>>> print repr(u'你好'.encode('utf-8')) # utf-8 編碼
'\xe6\xb5\xa3\xe7\x8a\xb2\xe3\x82\xbd'
附帶解決的問題:IDLE 的交互模式里無(wú)法輸入中文
我偶爾會(huì)用 Python 2 自帶的 IDLE 快速測(cè)試一兩行代碼,但在我的 Mac 下的 IDLE 交互模式里輸入中文會(huì)出現(xiàn)報(bào)錯(cuò):
>>> '中文'
Unsupported characters in input
這個(gè)問題在 v2ex 上同樣有同學(xué)問過: /t/44975 ,而他是在 Windows 下出現(xiàn)的,所以這個(gè)問題可能是普遍的。我原本以為這個(gè)問題同樣是因?yàn)樯鲜龅?stdin/stdout/stderr 的編碼問題而造成,就想順便解決掉。然而即使設(shè)置全局環(huán)境變量 PYTHONIOENCODING 為 utf-8 后仍舊不管用,IDLE 里輸入中文還是會(huì)報(bào)錯(cuò),sys.stdin.encoding 編碼還依舊是 us-ascii。
后來(lái)搜索后發(fā)現(xiàn),貌似這個(gè)問題是由 IDLE 輸入輸出的內(nèi)部實(shí)現(xiàn)機(jī)制導(dǎo)致的,可能跟 stdin/stdout/stderr 沒有關(guān)系。根據(jù)這里所說,IDLE 的交互模式下會(huì)根據(jù)機(jī)子的本地語(yǔ)言環(huán)境設(shè)置來(lái)判斷編碼,再用其對(duì)輸入進(jìn)行轉(zhuǎn)換后再執(zhí)行,而在我的 Mac 下這個(gè)編碼是 ascii,所以導(dǎo)致了 Unsupported characters in input。
而我搜到了一個(gè)可行的解決方法,其通過在 IDLE 的 IO 相關(guān)源碼(lib/python2.7/idlelib/IOBinding.py)中插入一行代碼強(qiáng)行覆蓋變量 encoding 的值為 'utf-8' 來(lái)解決這個(gè)問題。
不過后來(lái)經(jīng)過我測(cè)試后發(fā)現(xiàn),在 Mac 下其實(shí)更為簡(jiǎn)單的一個(gè)解決方法是,設(shè)置 IDLE 的環(huán)境變量 LANG 為 "en_US.UTF-8"。同樣我不想通過 launchctl 設(shè)置全局環(huán)境變量來(lái)解決,而我采用的解決方法是:
打開編輯 IDLE.app/Contents/MacOS/IDLE 文件。
在大概第 24 行的地方插入一行設(shè)置環(huán)境變量 LANG 的語(yǔ)句:
+ os.environ["LANG"] = "en_US.UTF-8" # 第 24 行
os.environ["PYTHONEXECUTABLE"] = executable
os.environ["DYLD_LIBRARY_PATH"] = libdir
保存文件,重新打開 IDLE 就可以在其交互模式里輸入中文了。
總結(jié)
以上是生活随笔為你收集整理的python3 rid1.7.4.2 控制台中文乱码_TL;DR - 有关 Python 2 和 Sublime Text 中文 Unicode 编码问题的分析与理解...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python搭配什么数据库_python
- 下一篇: python做excel表格柱状图_Py