Python2编码之殇

技术的探索,就好像编织故事一般,其乐趣在于偶尔能够讲述给别人听,并获得一些赞同!

Python编码问题一直困扰了我许久,之前有过一些总结,但并不系统,比较凌乱。当然python2.x编码问题本身,便是剪不断理还乱。本篇将系统介绍python2.x编程中会遇到的一些编码问题,并给出解决方案。基于对编码问题的摸索了解,我也尝试写了一个编码转换模块Transcode,应该能解决绝大部分新手的疑难杂症。当然,python大神可以绕道而行,至于使用3.x的朋友,以后将会成文介绍。

  python编程中会经常遇到操作系统编码、文件编码、控制台输入输出编码、网页编码、源代码编码、python编码,本文将会逐一介绍。首先我们来看看一些常见的编码情况:
1
2
3
4
5
print sys.getdefaultencoding() #系统默认编码
print sys.getfilesystemencoding() #文件系统编码
print locale.getdefaultlocale() #系统当前编码
print sys.stdin.encoding #终端输入编码
print sys.stdout.encoding #终端输出编码

将以上这段代码在windows与linux系统下分别运行,查看输出结果。
windows终端结果:

1
2
3
4
5
ascii
mbcs
('zh_CN', 'cp936')
cp936
cp936

Linux终端结果:

1
2
3
4
5
ascii
UTF-8
('zh_CN', 'UTF-8')
UTF-8
UTF-8

操作系统编码

  操作系统默认编码可以通过sys.getdefaultencoding()函数获取,可以看到windows与linux下默认都为ascii编码,而我们知道ascii编码不支持中文。那么操作系统编码将在python程序的何处会被用到呢?何时又会引发血案?

触发异常点

  经过测试,我发现当需要将unicode格式的字符串存入到文件时,python内部会默认将其先转换为Str格式的系统编码,然后再执行存入步骤。而在这过程中,容易引发ascii异常。
实例证明:

1
2
3
4
#! -*- coding:utf-8 -*-
a=u"中文"
f=open("test.txt","w")
f.write(a)

报错异常信息:UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 0-1……
说明:因为ascii不支持中文,而变量a为unicode格式的中文字符串,因此无法进行编码而引发异常。

解决方案

设置系统编码为utf-8或者gbk。

1
2
3
import sys
reload(sys)
sys.setdefaultencoding('gbk')

说明:在windows下将其设置为gbk,在linux在设置为utf-8.

终端编码

  windows下终端指的是控制台,在控制台上输入输出有着其本身的编码格式,如windows控制台输入输出编码都为cp936。原谅我是第一次看到此编码,于是上网查了会,发现其实它就是常见的GBK编码;而linux终端的输入输出编码都为utf-8。如果我们编写的程序,不会再终端输入输出任何内容,则可以忽略此编码,如若不然终端编码将会非常重要。

乱码点

我们在终端执行python脚本时,经常会遇到输出中文乱码,而这往往是因为输出的字符串本身编码与控制台编码不一致。
实例证明:

1
2
3
4
#! -*- coding:utf-8 -*-
a="中文" #定义一个变量,默认为Str,utf-8编码
print a
print type(a)

windows控制台输出结果:

1
2
浣犲ソ
<type 'str'>

linux终端输出结果:

1
2
中文
<type 'str'>

造成这种差异的原因在于windows控制台为gbk编码,而变量a本身为utf-8编码。

解决方案

1
2
3
4
#! -*- coding:utf-8 -*-
a='你好'
b=a.decode("utf-8").encode("gbk")
print b

将变量a从utf-8编码转换为gbk编码。

python编码

  python2.x从外部获取的内容都是string编码,其内部分为String编码与Unicode编码,而String编码又分为UTF-8,GBK,GB2312等等。因此为了避免不同编码造成的报错,python内部最好都转化为unicode编码,在输出时再转化为str编码 。可以用encode()/decode()函数,将string与unicode编码互换。

触发异常点

基本在于python内部变量编码与控制台编码,或者其他编码相结合时触发。
实例证明:

1
2
3
4
#! -*- coding:utf-8 -*-
a="中文" #定义一个变量,默认为str,utf-8编码
print a
print type(a)

运行结果:

1
2
浣犲ソ
<type 'str'>

  说明:windows下控制台输入输出都是gbk编码格式,而代码中定义的变量a为str,utf-8格式,所以会出现乱码。如果想创建一个unicode编码字符串的变量,则可以a=u”123”,在双引号前面加上一个u,表示a为unicode编码。

解决方案

1
2
3
#! -*- coding:utf-8 -*-
a='你好'
print a.decode("utf-8").encode("gbk")

  说明:首先我们定义的变量a是str格式,编码为utf-8的字符串,我们要将之转化为str格式,GBK编码的字符串。在python内部无法直接转化,需要借助decode()与encode()函数。decode()函数先将str格式的字符串a转化为unicode,再将unicode编码为str格式GBK。而在Unix系统下,不存在这个问题,因为都是utf-8编码,不会存在乱码。print语句默认会将unicode编码的字符串,encode为相应系统的str编码并输出(windows下为gbk,unix下为utf-8),因此不用担心print unicode编码字符串会报错。

源代码编码

源代码编码指的是python程序本身的编码,默认为ascii。

触发异常点

  python程序本身要被解释器解析执行,需要先被转化为二进制代码。而在这过程中容易引发异常,原因同样是ascii不支持中文,因此当python程序中出现中文时,哪怕是注释,也会引发ascii异常。
实例证明:

1
print "中文" #中文注释

报错:SyntaxError: Non-ASCII character ‘\xe7’……

解决方案

1
#! -*- coding:utf-8 -*-

python程序开头加上这句代码,指定python源代码编码格式为utf-8。

文件编码

  文件编码指的是,python程序从文件中获取的内容的编码格式。可以用sys.getfilesystemencoding()函数获取,windows下为mbcs,linux下为utf-8。至于mbcs,是一种多字节编码(没搞很明白)。

触发异常点(读取文件内容)

当python程序从文件中获取内容,并输出时,容易触发异常。
实例证明:

1
2
3
4
5
#! -*- coding:utf-8 -*-
f=open("test.txt","r")
content=f.read()
print type(content)
print content

运行结果:

1
2
<type 'str'>
你好

  可以看到windows下,从文件中读取的编码格式为Str,GBK格式(因为控制台输出没有中文乱码);而在Unix下为Str,Utf-8格式。从输出内容来说,并没有触发异常,然而当这些内容与python程序自身内容相结合时,容易触发异常。

解决方案

在windows下,最好将文件内容转为unicode,可以使用codecs:

1
f=codecs.open("test.txt", encoding='gbk').read()

将格式为gbk的文件内容转化为unicode格式,当然也可以直接使用open(“”,”r”).read().decode(“gbk”)

触发异常点(写入文件内容)

参考操作系统编码触发异常点,即将中文unicode字符写入文件时,容易触发异常。

解决方案

参考操作系统编码解决方案,或者手动将unicode编码转换为str编码。
实例证明:

1
2
3
4
#! -*- coding:utf-8 -*-
a=u"中文" #a为unicode格式编码
f=open("test.txt","w")
f.write(a.encode("gbk"))

当然如果变量a本身就是Str则不会报错,只是utf-8编码的内容写入windows文件中,显示会乱码。

网页编码

  网页编码,通常在写爬虫的时候经常遇到,再结合系统编码,python编码,文件编码,往往会搞得一团乱。在程序中我们应该分别处理这些编码,在python内部全部转化为unicode。那么网页编码又有哪些格式呢?
常见格式:utf-8,gbk,gb2312

触发异常点

还是在于从网页中获取的源码编码与终端编码,甚至python内部编码不一致的情况。
实例证明:

1
2
3
4
5
#!coding=utf-8
import urllib2
body=urllib2.urlopen('http://thief.one').read()
print type(body)
print body

运行结果:

1
2
<type 'str'>
body中文显示乱码

说明:这个网站的编码是utf-8,而且python从网页上爬取的内容都为Str格式,在windows控制台下输出会乱码。

解决方案

  依照之前做法,先将其转化为unicode。而相应的正则也可以为unicode编码,如:res=r’’+u”新成员”。可以通过chardet模块判断网页编码类型,返回的是一个带概率的字典。

编码判断

判断字符串编码

1
isinstance(obj, (str, unicode))

返回True或者False

判断网页编码

1
2
3
4
import chardet
import urllib2
body=urllib2.urlopen("http://thief.one").read()
chardet.detect(body)

判断编码格式,会有百分比,一般用来判断网页编码比较好。

判断系统编码

1
2
3
4
5
print sys.getdefaultencoding() #系统默认编码
print sys.getfilesystemencoding() #文件系统编码
print locale.getdefaultlocale() #系统当前编码
print sys.stdin.encoding #终端输入编码
print sys.stdout.encoding #终端输出编码

python2.x编码建议

  • 请尽量在Linux系统上编程,综上我们可以知道linux下较windows,编码问题良好很多。
  • python代码内部请全部使用unicode编码,在获取外部内容时,先decode为unicode,向外输出时再encode为Str
  • 在定义变量或者正则时,也定义unicode字符,如a=u”中文”;res=r””+u”正则”。

其他疑难杂症

实例一:

1
2
a="\\u8fdd\\u6cd5\\u8fdd\\u89c4"
print a

变量a的内容本身为unicode编码,怎么正常显示输入?
解决方案:

1
2
3
a="\\u8fdd\\u6cd5\\u8fdd\\u89c4" # unicode转化为中文
b=a.decode('unicode-escape')
print b



  如果阅读完本章,增加了您对python编码问题的认识,那我会感到欣慰,如有python编码上的问题可以在下方留言。
  如果阅读完本章,您仍然不知如何解决python乱码问题,没关系,请继续移步阅读Transcode解决python编码问题

  为了能够让您重视,我不得不再次重申:解决python2.x编码问题的关键,在于要明白无论从哪里来的内容,在python内部流通时,都应该先转换为unicode。(python3.x在这方面做了改进,并取得了很好的效果)

传送门

Python2编码之殇续集
Python3编码之美

本文标题:Python2编码之殇

文章作者:nMask

发布时间:2017年02月16日 - 12:02

最后更新:2017年07月25日 - 20:07

原始链接:http://thief.one/2017/02/16/解决Python2-x编码之殇/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

nMask wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!