下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

关于Python编码的那些事

作者:课课家教育     来源: http://www.kokojia.com点击数:958发布时间: 2016-03-28 16:31:23

标签: python编码中文编码python

大神带你学编程,欢迎选课

  以下是处理Python编码时出现的问题,事例中的人想用中文处理编码,于是很快出现了异常。为了解这个决问题,于是小编我找到了能解决中文编码问题的方法,希望能帮助到大家。

关于Python编码的那些事_python编码_中文编码_python_课课家

     在用Python写一些脚本的时候,尽管脚本的交互只是命令行+日志输出,但是为了让界面友好些,我还是决定用中文输出日志信息。

  很快,我就遇到了异常:

  

  为了解决问题,我花时间去研究了一下 Python 的字符编码处理。

  下面先复述一下 Python 字符串的基础,熟悉此内容的可以跳过。对应 C/C++ 的 char 和 wchar_t, Python 也有两种字符串类型,str 与 unicode:

 

  前面的申明:# -*- coding: utf-8 -*- 表明,上面的 Python 代码由 utf-8 编码。

  为了保证输出不会在 Linux 终端上显示乱码,需要设置好 linux 的环境变量:export LANG=en_US.UTF-8

  如果你和我一样是使用 SecureCRT,请设置 Session Options/Terminal/appearance/Character Encoding 为 UTF-8 ,保证能够正确的解码 linux 终端的输出。

  两个 Python 字符串类型间可以用 encode / decode 方法转换:

  

  为什么从 unicode 转 str 是 encode,而反过来叫 decode??

  因为 Python 认为 16 位的 unicode 才是字符的唯一内码,而大家常用的字符集如 gb2312,gb18030/gbk,utf-8,以及 ascii 都是字符的二进制(字节)编码形式。把字符从 unicode 转换成二进制编码,当然是要 encode。反过来,在Python中出现的str都是用字符集编码的ansi字符串。Python本身并不知道str的编码,需要由开发者指定正确的字符集decode。

  (补充一句,其实 Python 是可以知道 str 编码的。因为我们在代码前面申明了 # -*- coding: utf-8 -*-,这表明代码中的 str 都是用 utf-8 编码的,我不知道 Python 为什么不这样做。)

  如果用错误的字符集来 encode/decode 会怎样?

 

  这就遇到了我在本文开头贴出的异常:UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

  现在我们知道了这是个字符串编码异常。接下来, 为什么 Python 这么容易出现字符串编/解码异常?

  这要提到处理 Python 编码时容易遇到的两个陷阱。第一个是有关字符串连接的:

  

  简单的字符串连接也会出现解码错误?

  陷阱一:在进行同时包含 str 与 unicode 的运算时,Python 一律都把 str 转换成 unicode 再运算,当然,运算结果也都是 unicode。

  由于 Python 事先并不知道 str 的编码,它只能使用 sys.getdefaultencoding() 编码去 decode。在我的印象里,sys.getdefaultencoding() 的值总是 'ascii' ——显然,如果需要转换的 str 有中文,一定会出现错误。

  除了字符串连接,% 运算的结果也是一样的:

  

  我不理解为什么 sys.getdefaultencoding() 与环境变量 $LANG 全无关系。如果 Python 用 $LANG 设置 sys.getdefaultencoding() 的值,那么至少开发者遇到 UnicodeDecodeError 的几率会降低 50%。

  另外,就像前面说的,我也怀疑为什么 Python 在这里不参考 # -*- coding: utf-8 -*-?,因为 Python 在运行前总是会检查你的代码,这保证了代码里定义的 str 一定是 utf-8 。

  对于这个问题,我的唯一建议是在代码里的中文字符串前写上 u。另外,在 Python 3 已经取消了 str,让所有的字符串都是 unicode ——这也许是个正确的决定。

  其实,sys.getdefaultencoding() 的值是可以用“后门”方式修改的,我不是特别推荐这个解决方案,但是还是贴一下,因为后面有用:

  

     可以看到,问题魔术般的解决了。但是注意! sys.setdefaultencoding() 的效果是全局的,如果你的代码由几个不同编码的 Python 文件组成,用这种方法只是按下了葫芦浮起了瓢,让问题变得复杂。另一个陷阱是有关标准输出的。

     刚刚怎么来着?我一直说要设置正确的 linux $LANG 环境变量。那么,设置错误的 $LANG,比如 zh_CN.GBK 会怎样?(避免终端的影响,请把 SecureCRT 也设置成相同的字符集。)显然会是乱码,但是不是所有输出都是乱码。

     为什么是 unicode 而不是 str 的字符显示是正确的? 首先我们需要了解 print。与所有语言一样,这个 Python 命令实际上是把字符打印到标准输出流 —— sys.stdout。而 Python 在这里变了个魔术,它会按照 sys.stdout.encoding 来给 unicode 编码,而把 str 直接输出,扔给操作系统去解决。

  这也是为什么要设置 linux $LANG 环境变量与 SecureCRT 一致,否则这些字符会被 SecureCRT 再转换一次,才会交给桌面的 Windows 系统用编码 CP936 或者说 GBK 来显示。

  通常情况,sys.stdout.encoding 的值与 linux $LANG 环境变量保持一致:

 

  但是,这里有陷阱二:一旦你的Python代码是用管道 / 子进程方式运行,sys.stdout.encoding 就会失效,让你重新遇到 UnicodeEncodeError。

  比如,用管道方式运行上面的 example4.py 代码:

  

  可以看到,第一:sys.stdout.encoding 的值变成了 None。第二:Python 在 print 时会尝试用 ascii 去编码 unicode.

  由于 ascii 字符集不能用来表示中文字符,这里当然会编码失败。

  怎么解决这个问题? 不知道别人是怎么搞定的,总之我用了一个丑陋的办法:

  

  这个方法仍然有个副作用:直接输出中文 str 会失败,因为 codecs 模块的 writer 与 sys.stdout 的行为相反,它会把所有的 str 用 sys.getdefaultencoding() 的字符集转换成 unicode 输出。

 

  显然,sys.getdefaultencoding() 的值是 'ascii', 编码失败。

  解决办法就像 example3.py 里说的,你要么给 str 加上 u 申明成 unicode,要么通过“后门”去修改 sys.getdefaultencoding():

  

  总而言之,在 Python 2 下进行中文输入输出是个危机四伏的事,特别是在你的代码里混合使用 str 与 unicode 时。

  有些模块,例如 json,会直接返回 unicode 类型的字符串,让你的 % 运算需要进行字符解码而失败。而有些会直接返回 str, 你需要知道它们的真实编码,特别是在 print 的时候。

  为了避免一些陷阱,上文中说过,最好的办法就是在 Python 代码里永远使用 u 定义中文字符串。另外,如果你的代码需要用管道 / 子进程方式运行,则需要用到 example6.py 里的技巧。

赞(12)
踩(1)
分享到:
华为认证网络工程师 HCIE直播课视频教程