python乱码系列1

转载地址:http://in355hz.iteye.com/blog/1860787

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

下面先复述一下 Python 字符串的基础,熟悉此内容的可以跳过。

Python 有两种字符串类型,str 与 unicode:

    # -*- coding: utf-8 -*-  
    # file: example1.py  

    # 这个是 str 的字符串  
    s = '关关雎鸠'  

    # 这个是 unicode 的字符串  
    u = u'关关雎鸠'  

    print isinstance(s, str)      # True  
    print isinstance(u, unicode)  # True  

str.class

使用str.class可以查看str的编码形式

    print s.__class__   # <type 'str'>  
    print u.__class__   # <type 'unicode'>  

终端

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

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

-- coding: utf-8 --的作用

其实 Python 是可以知道 str 编码的。因为我们在代码前面申明了 # -- coding: utf-8 --,这表明代码中的 str 都是用 utf-8 编码的,但 Python 并没有这么做。
因为顶部的:# -- coding: utf-8 --只有这三个作用。

如果代码中有中文注释,就需要此声明
比较高级的编辑器(比如我的emacs),会根据头部声明,将此作为代码文件的格式。
程序会通过头部声明,解码初始化 u”人生苦短”,这样的unicode对象,(所以头部声明和代码的存储格式要一致)

encode/decode

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

    # 从 str 转换成 unicode  
    print s.decode('utf-8')   # 关关雎鸠  

    # 从 unicode 转换成 str  
    print u.encode('utf-8')   # 关关雎鸠  

这里写图片描述
字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。但是,Python 2.x的默认编码格式是ASCII,就是说,在没有指定Python源码编码格式的情况下,源码中的所有字符都会被默认为ASCII码。也因为这个根本原因,在Python 2.x中经常会遇到UnicodeDecodeError或者UnicodeEncodeError的异常

    # 用 ascii 编码含中文的 unicode 字符串  
    u.encode('ascii')  # 错误,因为中文无法用 ascii 字符集编码  
 # UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)  

    # 用 gbk 编码含中文的 unicode 字符串  
    u.encode('gbk')  # 正确,因为 '关关雎鸠' 可以用中文 gbk 字符集表示  
    # '\xb9\xd8\xb9\xd8\xf6\xc2\xf0\xaf'  
    # 直接 print 上面的 str 会显示乱码,修改环境变量为 zh_CN.GBK 可以看到结果是对的  

    # 用 ascii 解码 utf-8 字符串  
    s.decode('ascii')  # 错误,中文 utf-8 字符无法用 ascii 解码  
    # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

    # 用 gbk 解码 utf-8 字符串  
    s.decode('gbk')  # 不出错,但是用 gbk 解码 utf-8 字符流的结果,显然只是乱码  
    # u'\u934f\u51b2\u53e7\u95c6\u5ea8\u7b2d'  

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

sys.getdefaultencoding()

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

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

    # -*- coding: utf-8 -*-  
    # file: example2.py  

    # 这个是 str 的字符串  
    s = '关关雎鸠'  

    # 这个是 unicode 的字符串  
    u = u'关关雎鸠'  

    s + u  
    # 失败,UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

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

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

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

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

Python代码

# 正确,所有的字符串都是 str, 不需要 decode  
"中文:%s" % s   # 中文:关关雎鸠  

# 失败,相当于运行:"中文:%s".decode('ascii') % u  
"中文:%s" % u  # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

# 正确,所有字符串都是 unicode, 不需要 decode  
u"中文:%s" % u   # 中文:关关雎鸠  

# 失败,相当于运行:u"中文:%s" % s.decode('ascii')  
u"中文:%s" % s  # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

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

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

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


    # -*- coding: utf-8 -*-  
    # file: example3.py  
    import sys  

    # 这个是 str 的字符串  
    s = '关关雎鸠'  

    # 这个是 unicode 的字符串  
    u = u'关关雎鸠'  

    # 使得 sys.getdefaultencoding() 的值为 'utf-8'  
    reload(sys)                      # reload 才能调用 setdefaultencoding 方法  
    sys.setdefaultencoding('utf-8')  # 设置 'utf-8'  

    # 没问题  
    s + u  # u'\u5173\u5173\u96ce\u9e20\u5173\u5173\u96ce\u9e20'  

    # 同样没问题  
    "中文:%s" % u   # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'  

    # 还是没问题  
    u"中文:%s" % s  # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'  

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

输出乱码

另一个陷阱是有关标准输出的。

刚刚怎么来着?我一直说要设置正确的 linux LANG LANG,比如 zh_CN.GBK 会怎样?(避免终端的影响,请把 SecureCRT 也设置成相同的字符集。)

显然会是乱码,但是不是所有输出都是乱码。

    # -*- coding: utf-8 -*-  
    # file: example4.py  

    # 这个是 str 的字符串  
    s = '关关雎鸠'  

    # 这个是 unicode 的字符串  
    u = u'关关雎鸠'  

    # 输出 str 字符串, 显示是乱码  
    print s   # 鍏冲叧闆庨笭  

    # 输出 unicode 字符串,显示正确  
    print u  # 关关雎鸠  

为什么是 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代码

    # -*- coding: utf-8 -*-  
    # file: example5.py  
    import sys  

    # 检查标准输出流的编码  
    print sys.stdout.encoding  
    # 设置 $LANG = zh_CN.GBK,  输出 GBK  
    # 设置 $LANG = en_US.UTF-8,输出 UTF-8  

    # 这个是 unicode 的字符串  
    u = u'关关雎鸠'  

    # 输出 unicode 字符串,显示正确  
    print u  # 关关雎鸠  

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

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

    python -u example5.py | more  

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

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

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

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

Python代码 收藏代码

# -*- coding: utf-8 -*-  
# file: example6.py  
import os  
import sys  
import codecs  

# 无论如何,请用 linux 系统的当前字符集输出:  
if sys.stdout.encoding is None:  
    enc = os.environ['LANG'].split('.')[1]  
    sys.stdout = codecs.getwriter(enc)(sys.stdout)  # 替换 sys.stdout  

# 这个是 unicode 的字符串  
u = u'关关雎鸠'  

# 输出 unicode 字符串,显示正确  
print u  # 关关雎鸠  

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


    # 这个是 str 的字符串  
    s = '关关雎鸠'  

    # 输出 str 字符串, 异常  
    print s   # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

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

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


    # 使得 sys.getdefaultencoding() 的值为 'utf-8'  
    reload(sys)                      # reload 才能调用 setdefaultencoding 方法  
    sys.setdefaultencoding('utf-8')  # 设置 'utf-8'  

    # 这个是 str 的字符串  
    s = '关关雎鸠'  

    # 输出 str 字符串, OK  
    print s   # 关关雎鸠  

总而言之,在 Python 2 下进行中文输入输出是个危机四伏的事,特别是在你的代码里混合使用 str 与 unicode 时。
有些模块,例如 json,会直接返回 unicode 类型的字符串,让你的 % 运算需要进行字符解码而失败。而有些会直接返回 str, 你需要知道它们的真实编码,特别是在 print 的时候。
为了避免一些陷阱,上文中说过,最好的办法就是在 Python 代码里永远使用 u 定义中文字符串。另外,如果你的代码需要用管道 / 子进程方式运行,则需要用到 example6.py 里的技巧。

(完)
乱码例子一:
在开发自用爬虫过程中,有的网页是utf-8,有的是gb2312,有的是gbk,如果不加处理,采集到的都是乱码,解决的方法是将html处理成统一的utf-8编码

版本python2.7

#coding:utf-8
import chardet
#抓取网页html
line = "http://www.pythontab.com"
html_1 = urllib2.urlopen(line,timeout=120).read()
encoding_dict = chardet.detect(html_1)
print encoding
web_encoding = encoding_dict['encoding']
#处理,整个html就不会是乱码。
if web_encoding == 'utf-8' or web_encoding == 'UTF-8':
html = html_1
else :
html = html_1.decode('gbk','ignore').encode('utf-8')
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值