TL;DR

问题背景:

相信很多用 Sublime Text 来写 Python 2 的同学都遇到过以下这个问题(例如这位同学 /t/100435 和这位同学/t/163012 ):

在 Sublime Text 里用 Cmd (Ctrl) + B 运行代码 print u'中文',想要打印出 unicode 类型的字符串时,会出现以下报错:

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

传说中的 Python 2 编码坑(笑)

而同样的 print u'中文' 代码在 Mac 的终端里却能正常打印出 “中文” 结果,没有任何报错。

虽然在网上能查到多种解决方法,但一直以来知其然而不知其所以然,不了解为什么那些方法能解决问题的真正原因,也不知道为什么同样的代码在终端里就可以运行而在 Sublime Text 里就不行了?

因此我研究学习了下这个问题相关的一些 Python 2 编码问题,在这里分享下我的理解。

以下属于新手向,参考了网上多篇文章,如有错误,望指正。

先说下我的环境:

Mac OS X

Python 2.7

Sublime Text 3

分析:

Python 在向控制台 (console) print 的时候,因为控制台只能看得懂由 bytes(字节序列)组成的字符串,而 Python 中 "unicode" 对象存储的是 code points(码点),因此 Python 需要将输出中的 "unicode" 对象用编码转换为储存 bytes(字节序列)的 "str" 对象后,才能进行输出。

而在报错里看到 UnicodeEncodeError, 那就说明 Python 在将 unicode 转换为 str 时使用了错误的编码。而为什么是 'ascii' 编码呢?那是因为 Python 2 的默认编码就是 ASCII,可以通过以下命令来查看 Python 的默认编码:

>>> import sys

>>> print sys.getdefaultencoding()

ascii

所以此时在 Sublime Text 里运行 print u'中文',实际上等于是运行了:

print u'中文'.encode('ascii')

ASCII 编码无法对 unicode 的中文进行编码,因此就报错了。

那为什么同样的代码 print u'中文' 在 Mac 的终端里却能正常输出中文,难道是因为终端下的 Python 2 的默认编码不是 ASCII?非也,在终端下运行 sys.getdefaultencoding() 结果一样是 ascii。那同样是 ascii 为什么会有不同的结果?难倒这里 Python 用了另外一个编码来转换?

是的,其实 Python 在 print unicode 时真正涉及到的是另一组编码:stdin/stdout/stderr 的编码,也就是标准输入、标准输出和标准错误输出的编码。可以通过以下命令来查看,这里是在我的终端下运行的结果:

>>> import sys

>>> print sys.stdin.encoding

UTF-8

>>> print sys.stdout.encoding

UTF-8

>>> print sys.stderr.encoding

UTF-8

在正常情况下,Python 2 在 print unicode 时用来转换的编码并不是 Python 的默认编码 sys.getdefaultencoding(),而是 sys.stdout.encoding 所设的编码。

因为在我的终端下 Python 的 sys.stdout.encoding 编码是 UTF-8,所以在终端里运行 print u'中文' 时,实际上是等于运行了:

print u'中文'.encode('UTF-8')

编码正确,运行正常,因此没有报错。

在类 UNIX 系统下,Python 应该是通过环境变量 LC_CTYPE 来判断 stdin/stdout/stderr 的编码的。因此一般只要将 shell 的 LANG 环境变量设置对为 **_**.UTF-8 后,应该就能在终端里直接 print 出 unicode 类型的字符串了,而不需要在 print 时手动加上 .encode('utf-8') 进行编码了。

但在 Sublime Text 里事情就没那么美好了。在 Sublime Text 里运行查看 stdout 编码的命令,发现:

import sys

print sys.stdout.encoding

-----------------------------"""

None

[Finished in 0.1s]

结果甚至不是 'ascii' 而是 None。可能是因为 Sublime Text 的 Build System 是用 subprocess.Popen 来运行 Python 的,导致 Python 无法判断出正确的 stdin/stdout/stderr 编码,于是都变成 None 了。

这种情况也发生在输出的目标是管道的情况下:

$python -c 'import sys; print sys.stdout.encoding' | tee /tmp/foo.txt

None

那么在这种 sys.stdout.encoding 为 None 情况下的 print unicode 怎么办呢?答案就是 Python 只能很无奈地使用 sys.getdefaultencoding() 的默认编码 ascii 来对 unicode 进行转换了。这样就出现了本文开头所说的那个 UnicodeEncodeError 问题。

总结一下 Python 2 向控制台 print 输出时的流程:

Python 启动时,当它发现当前的输出是连接到控制台的时候,它会根据一些环境变量,例如环境变量 LC_CTYPE,来设法判断出 sys.stdin/stdout/stderr.encoding 编码值。

当 Python 无法判断出所需的编码时,它会将 sys.stdin/stdout/stderr.encoding 的值设置为 None。

print 时判断字符串是否是 unicode 类型。

如果是的话,并且 sys.stdout.encoding 不为 None 时,就使用 sys.stdout.encoding 编码对 unicode 编码成 str 后输出。

如果 sys.stdout.encoding 为 None 的话,就使用 sys.getdefaultencoding() 默认编码来对 unicode 进行转换成 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 刚启动时更改了 Python 的默认编码为 utf-8。此后:

>>> print sys.getdefaultencoding()

utf-8

但就本文所讨论的问题来说,这个方法并不是真正地直接解决了问题。就如上述所说,Python 只是在 sys.stdout.encoding 为 None 时才会使用默认编码来转换需要 print 的 unicode 字符串。那万一在 sys.stdout.encoding 存在,但为 ascii 的情况下呢?这样即使更改了 Python 的默认编码,同样还是会出现 UnicodeEncodeError 报错。所以对本问题来说,这个方法治标不治本。

除此之外,很多人都用这个方法来解决 Python 2 下遇到的其它各种各样的编码问题,在 v2ex 的各种 Python 编码问题讨论帖中也常常能见到有人推荐用这个方法来解决问题的。

但实际上很多大牛都不推荐用这个方法来解决 Python 2 的编码问题,这里引用下 StackOverflow 相关回答 里的一句话:

the use of sys.setdefaultencoding() has always been discouraged

为什么这个方法不被推荐呢?我们来看下 Python 文档里对这个 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.

可以看到这个方法原本就不是用户向的方法,并没有打算让用户用这个方法来更改 Python 2 的默认编码。

那为什么不建议我们更改 Python 的默认编码呢?

这里引用 Python 核心开发者、Python Unicode 支持的设计者和实现者: Marc-André Lemburg,他在一个邮件列表上的回复:

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 唯一支持的内部编码只有 ASCII,更改其默认编码为其它编码可能会导致各种各样奇怪的问题。在这里他也说了使用 sys.setdefaultencoding() 的方法是彻彻底底的错误,正确的方法应该是更改 stdout 和 stdin 的编码。

所以这个方法是最不正确的填坑方法,请大家慎用。

解决方法 2:

然后说说应当是姿势最正确的、也是大家都懂的方法:

在 print 的时候显式地用正确的编码来对 unicode 类型的字符串进行 encode('正确的编码') 为 str 后, 再进行输出。

而在 print 的时候,这个正确的编码一般就是 sys.stdout.encoding 的值。但也正如上述所说,这个值并不是一直是可靠的,因此需要根据所使用的平台和控制台环境来判断出这个正确的编码。

而在 Mac 下这个正确的编码一般都是 utf-8,因此若不考虑跨环境的话,可以无脑地一直用 encode('utf-8') 和 decode('utf-8') 来进行输入输出转换。

在我的经验中,这个策略也是解决 Python 2 其它 unicode 相关编码问题的最佳方法。在 PyCon 2012 的一个演讲中(关于 Python Unicode 问题很好的一个演讲,这里有演讲稿的中文翻译版),对这个方法有一个很形象的比喻:

因为在程序中进进出出的只有存储 bytes(字节序列)的 str。因此最好的策略是将输入的 bytes 马上解码成 unicode,而在程序内部中均使用 unicode,而当在进行输出的时候,尽早将之编码成 bytes。

也就是要形成一个 Unicode 三明治(如图), bytes 在外, Unicode 在内。在边界的地方尽早进行 decode 和 encode。不要在内部混用 str 和 unicode,尽可能地让程序处理的字符串都为 Unicode。

解决方法 3:

虽然解决方法 2 是最正确的方式,但是有时候在 Sublime Text 里调试些小脚本,实在是懒得再在每个 print 语句后面写一个尾巴 .encode('utf-8')。那么有没有办法能让 Sublime Text 像在终端里一样直接就能 print u'中文' 呢?也就是说能不能解决 sys.stdin/stdout/stderr.encoding 为 None 的情况呢?

答案肯定是有的,一种方法是用类似更改默认编码的方法一样,用 dirty hack 的方式在 Python 代码中去显式地更改 sys.stdin/stdout/stderr.encoding 的值。一样是不推荐,我也没尝试过,在这里就不详说了。

另一种方法则是通过设置 PYTHONIOENCODING 环境变量来强制要求 Python 设置 stdin/stdout/stderr 的编码值为我们想要的,这是一个相对比较干净的解决方法。见文档:

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 下对全局 GUI 程序设置环境变量的方法是:使用 launchctl setenv <, ...> 命令对所有 launchd 启动的未来子进程设置环境变量。

在这里顺便科普下,为什么对所有 launchd 启动的未来子进程设置环境变量可以使得对 Mac 下所有 GUI 程序生效。这是因为 launchd 是 OS X 系统启动后运行的第一个非内核进程。我们可以在 activity monitor(活动监视器)里看到,它的 pid 是很帅气的 1。而之后所有的进程都将是它的子进程。

另外还可以通过 launchd 在 Mac 下实现类 crontab 的功能。

launchctl setenv 命令设置的全局环境变量会在电脑重启后失效,因此就需要通过上面说的 launchd 的开机启动任务的功能来在重启后再设置一遍环境变量,其配置方法可以参考这里。也因为这个原因,我并没有使用这个方法来设置 PYTHONIOENCODING 环境变量。

而 Sublime Text 提供了一个设置 Build System 环境变量的方法,这个方法各平台的 Sublime Text 都适用。

设置 Sublime Text 的 Python Build System 环境变量的步骤如下:

将 Sublime Text 默认的 Python Build System 的配置文件 Python.sublime-build(找到这个文件的最好方法是安装插件 PackageResourceViewer)复制一份到 Sublime Text 的 /Packages/User 文件夹下(在 Mac 和 Sublime Text 3 下这个路径是 ~/Library/Application Support/Sublime Text 3/Packages/User)。

打开编辑新复制来的 Python.sublime-build 文件,如下加上一行设置 PYTHONIOENCODING 环境变量为 UTF-8 编码的内容,并保存:

{

"shell_cmd": "python -u \"$file\"",

"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",

+ "env": {"PYTHONIOENCODING": "utf8"},

"selector": "source.python"

}

这样一来终于在这么长的文章后能在 Sublime Text 里直接运行 print u'中文',而不用再出现万恶的 UnicodeEncodeError 了。

既然都研究到这了,不妨我们试试把 PYTHONIOENCODING 设置成其它编码看看会出现什么情况,例如设置成简体中文 Windows 的默认编码 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 下可能会出现的问题(例如这两位同学 /t/45391 /t/88428 )。这是因为 Sublime Text 的 Build System 默认是用 utf-8 编码去解读运行的输出的,而我们指定了让 Python 用 cp936 编码来生成 str 字符串进行输出,那么就会出现 Sublime Text 无法识别输出的情况了。

同样在对终端 export PYTHONIOENCODING=cp936 后,在终端下 print u'你好' 输出的就会是 ��� 这样的乱码。

解决办法之一就是同样在 Python.sublime-build 文件里设置 "env": {"PYTHONIOENCODING": "utf8"} 来使得输出统一为 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"

}

那我们再试试把这两个设置同时都加到 Python.sublime-build 文件里,也就是让 Python 输出 utf8 编码的字符串,而让 Sublime Text 用 cp936 编码来解读,看看会发生什么情况?

{

"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],而是这么喜感的 “浣犲ソ”!

这是因为 “你好” 的 utf-8 编码刚好和 “浣犲ソ” 的 cp936 编码重合了,都是 '\xe6\xb5\xa3\xe7\x8a\xb2\xe3\x82\xbd',所以使用 cp936 编码去解读的 Sublime Text 就认为这段字符串就是 “浣犲ソ” 而显示了出来。

>>> 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 的交互模式里无法输入中文

我偶尔会用 Python 2 自带的 IDLE 快速测试一两行代码,但在我的 Mac 下的 IDLE 交互模式里输入中文会出现报错:

>>> '中文'

Unsupported characters in input

这个问题在 v2ex 上同样有同学问过: /t/44975 ,而他是在 Windows 下出现的,所以这个问题可能是普遍的。我原本以为这个问题同样是因为上述的 stdin/stdout/stderr 的编码问题而造成,就想顺便解决掉。然而即使设置全局环境变量 PYTHONIOENCODING 为 utf-8 后仍旧不管用,IDLE 里输入中文还是会报错,sys.stdin.encoding 编码还依旧是 us-ascii。

后来搜索后发现,貌似这个问题是由 IDLE 输入输出的内部实现机制导致的,可能跟 stdin/stdout/stderr 没有关系。根据这里所说,IDLE 的交互模式下会根据机子的本地语言环境设置来判断编码,再用其对输入进行转换后再执行,而在我的 Mac 下这个编码是 ascii,所以导致了 Unsupported characters in input。

而我搜到了一个可行的解决方法,其通过在 IDLE 的 IO 相关源码(lib/python2.7/idlelib/IOBinding.py)中插入一行代码强行覆盖变量 encoding 的值为 'utf-8' 来解决这个问题。

不过后来经过我测试后发现,在 Mac 下其实更为简单的一个解决方法是,设置 IDLE 的环境变量 LANG 为 "en_US.UTF-8"。同样我不想通过 launchctl 设置全局环境变量来解决,而我采用的解决方法是:

打开编辑 IDLE.app/Contents/MacOS/IDLE 文件。

在大概第 24 行的地方插入一行设置环境变量 LANG 的语句:

+ os.environ["LANG"] = "en_US.UTF-8" # 第 24 行

os.environ["PYTHONEXECUTABLE"] = executable

os.environ["DYLD_LIBRARY_PATH"] = libdir

保存文件,重新打开 IDLE 就可以在其交互模式里输入中文了。

python3 rid1.7.4.2 控制台中文乱码_TL;DR - 有关 Python 2 和 Sublime Text 中文 Unicode 编码问题的分析与理解...相关推荐

  1. python -m json.tool 中文乱码 Format JSON with python

    python -m json.tool 中文乱码 Format JSON with python 现在以 json 为数据传输格式的 RESTful 接口非常流行.为调试这样的接口,一个常用的办法是使 ...

  2. PLSQL Developer 插入中文 乱码问题,如图 这个是由于oracle服务器端字符编码 和 Oracle 客户端 字符编码不一致引起的。 检查Oracle服务器端字符编码,用 sel

    PLSQL Developer 插入中文 乱码问题,如图 这个是由于oracle服务器端字符编码 和 Oracle 客户端 字符编码不一致引起的. 检查Oracle服务器端字符编码,用 select ...

  3. 解决SpringMVC中文乱码问题 -----这是服务器返回参数到前端中文乱码

    解决SpringMVC中文乱码问题 -----这是服务器返回参数到前端中文乱码 参考文章: (1)解决SpringMVC中文乱码问题 -----这是服务器返回参数到前端中文乱码 (2)https:// ...

  4. 解决 Linux 下 Sublime Text 中文输入

    2019独角兽企业重金招聘Python工程师标准>>> 完美解决 Linux 下 Sublime Text 中文输入 完美解决 Linux 下 Sublime Text 中文输入 转 ...

  5. Sublime Text - 中文汉化

    Sublime Text - 中文汉化 一.下载Sublime Text 二.打开Sublime Text 三.汉化Sublime Text 一.下载Sublime Text 二.打开Sublime ...

  6. python 折线图中文乱码_彻底解决 Python画图中文乱码问题--Pyplotz组件

    1 源起 自从开始学习Python,就非常喜欢用来画图.一直没有需求画要中文显示信息的图,所以没有配置Python中文的环境.由于昨天就需要画几十个形式相同,只是数据不同的图,并且需要显示中文信息.如 ...

  7. eclipse中文乱码解决_如何解决firefly rk3399 ubuntu 系统中文乱码

    用过上述板子系统的同学,当你打开firefly ubuntu 系统浏览器,连上外网,打开一个网站,是否会发现如果你是原始官方镜像,这个网站的中文会出现乱码现象,其实这是因为原始镜像的firefly u ...

  8. java中文乱码问题的原因是什么?怎么解决中文乱码问题?

    1.中文乱码问题是由于用户的输入和输出的编码格式不同造成的. 2.在编译servlet类源程序时用encoding指定编码格式GBK或者GB2312,用户输入部分:用response对象的setCon ...

  9. Sublime Text中文乱码

    我们在使用Sublime Text打开java代码的时候中文内容显示的是乱码,那么我们该如何解决的呢? 一: 安装包管理器 按Ctrl+~快捷键或者通过View->Show Console菜单打 ...

最新文章

  1. File Filter用法
  2. 英特尔的指令集体系结构_对标英特尔的RISC-V大有可为,CPU三分天下格局可期
  3. hdu4847:Wow! Such Doge!(字符串匹配)
  4. 029_SpringBoot整合JPa
  5. 整数的幂计算(三种方法)最快O(logn)
  6. 【机器学习基础】时间序列基本概念
  7. 程序员怎样才能写出一篇好的技术文章
  8. 关于Mysql DATE_FORMAT() 日期格式
  9. 7个免费的Linux FTP客户端工具
  10. Microsoft宣布将停止支持多个 .NET Framework版本
  11. RN与原生交互(一)——基本页面跳转
  12. python 二进制,十进制,十六进制
  13. mysql 矩表_mysql表某相同值最近一次出现的间距
  14. LINUX 编译安装 PHP 环境
  15. 用给出的Cramer规则解线性方程组
  16. excel概率密度函数公式_使用Excel绘制t分布概率密度函数
  17. 人工智能--自然演绎推理
  18. NPM Error: Cannot find module ‘caniuse-lite/data/features/css-unicode-bidi.js‘
  19. 个人对于区块链技术的理解
  20. 国籍SQL—— 看自己需求 ( 港澳台 也独立出来了)

热门文章

  1. VUE:解决判断网页端与手机端情况下,横竖屏无法判断的问题
  2. led拼接屏报价_液晶拼接屏与led显示屏的区别在哪?
  3. dlib 怎么安装vs2017_win10中的dlib库安装过程
  4. (二十七)【2021 WWW】Learning Intents behind Interactions with Knowledge Graph for Recommendation
  5. 谈谈怎样提高炼丹手速
  6. 数据缺失、混乱、重复怎么办?最全数据清洗指南让你所向披靡
  7. 计算机史上首篇教你从算法问题提炼算法思想的文章
  8. 论文浅尝 - ACL2022 | 基于多语言语义解耦表示的跨语言迁移方法实现多语言阅读理解...
  9. 论文浅尝 - ACL2021 | 探讨跨句事件联合抽取问题
  10. 论文浅尝 | 一种可解释的语义匹配复值网络