python编码之痛

在python编程中,常常会出现一些编码上的问题,大致包括这三类问题: SyntaxError: Non-ASCII characterUnicodeDecodeErrorUnicodeEncodeError。下面我对日常遇到的编码问题做的一些小结,并解释上述三类问题出现的原因,并对日常编码做了一些规范化的建议,来避免上述这三类问题。

python2.x 的编码解码介绍

当对str进行编码(encode)时,会先用 默认编码 将str解码(decode)成unicode,然后再将unicode编码为你指定的编码。

那么由于python2.x的默认编码(通过 sys.getdefaultencoding() 获取)是ascii,那么在处理中文的时候,因为ascii能够表示的字符范围有限,所以解码中文会有问题,常常报如下错误,例如

string = "中国"
print(s.encode('gbk'))  # 等价于 print(s.decode('ascii').encode('gbk'))

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

如果要想正常输出,可以指定python的默认编码样式为 utf-8,当然也可以不修改python的默认编码,而是在string的编码前面先显性的执行解码命令,并且指定解码样式为 utf-8,例如

import sys

reload(sys)
sys.setdefaultencoding('utf-8')

string = "中国"
print(s.encode('gbk'))  # 等价于 print(s.decode('utf-8').encode('gbk'))

所以使用 unicode 命令创建 unicode 对象时,如果不说明这个字符串的编码格式,那么程序会使用python的默认编码,例如如下代码,如果没有设置默认编码的话,也会出现上面的 UnicodeDecodeError 错误

string = unicode("中国")  # 等价于 string = unicode("中国", sys.getdefaultencoding())

python2.x 的文件头声明编码

我们在写python2.x文件时,一般会在文件头声明编码,例如如下代码

# -*- coding: utf-8 -*-

# 或者是如下代码,注意下面 = 符号两边没有空格

# coding=utf-8

做这个编码声明,是因为文件中有中文字符(不论是注释内容,还是字符串赋值)时,python2.x才知道如何去解析这些字符串,否则会出现如下错误

SyntaxError: Non-ASCII character '\xe4' in file test.py on line 2, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

另外做这个编码声明,python2.x会解码初始化 u"中国"unicode 对象

有人会发现,我按照这样在文件头部声明了编码,怎么我在执行 print(u"中国") 的时候,会报如下错误呢?

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

出现这样的报错,是由于服务器的输出编码格式不能完成对 u"中国" 字符串的编码操作,通过shell命令 locale 查看到服务器的输出编码格式为 en_US (或者是通过python命令 sys.stdout.encoding 查看到为 ISO-8859-1,该编码和ascii编码相似,且Latin1是ISO-8859-1的别名),而 ascii 能够表示的字符范围有限,不能对所有的unicode完成编码操作。

多重编码的字符串混合

如果我们将多重编码格式的字符串进行混合使用时,下面将介绍这种情况。
就像 int 和 float 类型的数据进行运算时,结果为 float,那么多重编码的字符串混合使用的时候,结果是什么格式的呢?

python2在处理unicode字符串和str类字符串时,是将str类字符串转化为unicode字符串。例如如下例子

string_1 = "Hello " + "中国"  # "Hello" 为ascii编码,"中国" 为utf-8编码,得到的结果为 utf-8编码

string_2 = "Hello " + "中国" + u"美食" # u"美食"为unicode字符串,所以最终的结果应该是unicode编码,"Hello "容易decode为unicode,但是 "中国" 受制于python的默认编码(sys.getdefaultencoding())如果该值不是 utf-8 会报 UnicodeDecodeError 错误

string_3 = "Hello " + "中国" + "美食".decode('utf-8').encode('gbk')  # ascii/utf-8/gbk 这三种不同编码的str类字符串相加,得到的结果是gbk编码的字符串

关于编码的字节长度

在计算字符串的字节长度的时候,使用命令 len,我们知道ascii编码的字符,一个该字符由1个字节组成;utf-8编码的字符,一个该字符由3个字节组成;gbk编码的字符,一个该字符由2个字节组成。也正是由于gbk编码的字符串所占用的字节少,所以一些网站就使用gbk编码。

那我们对unicode的字符串使用 len 的话,得到的结果是字符数,不同于上面的字节数。

具体可以查看下面的例子:

# python2.x
string = "中国"  # string编码为utf-8

print(len(string))  # 6个字节 (= 2 * 3)
print(len(string.decode('utf-8')))  # 2个字符 通过decode得到的是unicode字符串,通过len是在计算字符的数量
print(len(string.decode('utf-8').encode('gbk')))  # 4个字节 (= 2 * 2) 通过decode和encode得到的是gbk编码的字符串

string = u"中国"  # string编码为unicode

print(len(string))  # 2个字符
print(len(string.encode('utf-8')))  # 6个字节 (= 2 * 3)将string编码为utf-8字符串
print(len(string.encode('gbk')))  # 4个字节 (= 2 * 2)  将string编码为gbk字符串

关于requests库

我们在爬虫或者是测试服务器响应数据的时候,常常使用requests库来操作,其中Request对象在访问服务器后返回一个Response对象,这个对象将返回的http响应字节码保存到content属性中,但是如果访问另外一个属性text时,会返回一个unicode对象,乱码就常常发生在这里,造成显示乱码。

因为Response对象会通过另外一个属性encoding来将字节码编码成unicode,而这个encoding属性是reponse通过chardet猜出来的,例如如果爬取一个 gbk 网页的内容,就会出现乱码,可以通过如下方法解决:

import requests

url = "https://pvp.qq.com"
response = requests.get(url)
response.encoding = 'gbk'
print(response.text)

编码维护习惯

基于以上的编码问题,这里列出一些我们在日常处理字符的一些规范,以此来统一我们的字符编码解码习惯,减少因为字符串编码的问题。

  1. 设置python的默认编码为 utf-8 (python2.x为 ascii)
  2. python代码文件的保存格式要与文件头部的声明编码保持一致,为 utf-8
  3. 如果是中文,程序内部尽量使用 unicode 而不是 str
  4. 保持程序内外的编码统一,即程序内只使用unicode,那么在从外部读取文件时,一定要将这些字节流转化为unicode,这样在后面的代码中只会去处理unicode,而不是str,例如如下的读取文件代码。
# python2.x
import codecs
with codecs.open('text.py', 'r', encoding='utf-8') as f:
    for i in f:
        print(i)

# python3.x
with open('text.py', 'r', encoding='utf-8') as f:
    for i in f:
        print(i)

python2.x 与 python3.x的编码区别

字符串的类型(type),在python2.x和python3.x中有不同的表示,python2.x的编码分为str和unicode,而python3.x的编码分为bytes和str,且他们是相互对应的,即python2.x的 str 对应的是python3.x的 bytes;python2.x的 unicode 对应的是 python3.x的 str

python2.x中所有的 ascii/utf-8/gbk 等编码的字符都是str类字符串,而python2.x默认的字符编码格式是ascii,str类字符串转换时,都需要通过unicode作为媒介。

python3.x中所有的 ascii/utf-8/gbk 等编码的字符都是bytes类字符串,而python3.x默认的字符串编码格式是unicode,并且python3.x中str类字符串就是unicode类型的字符串。

参考资料

  1. unicode之痛
  2. python2.x与python3.x编码区别
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343