谈谈python字符编码问题

ascii编码


ASCII可以编码英文字符,0-9数字,以及一些可打印字符,不可打印字符等。
在那个时候,编写程序只使用这个编码就足够,一个字符通过写入后,在内存中保存为其对应的ASCII编号,当从内存中读出时,把对应的ASCII编号转换成对应的字符即可。

UNICODE编码


等到各个国家开始使用计算机后,就有一个问题,如何将本国文字也可以在计算中存储和显示?

ASCII由于最开始只用一个字节后7位,可以表示128个字符,但是世界上的国家那么多,文字又千奇百怪,怎么表示这些所有的字符。这个就产生了unicode编码。unicode把世界上所有的字符进行了编号。因为字符数量很多,所以unicode编码的编号需要用到到2个,3个甚至更多的字节。现在对应的字符编号有了。接下来,如果我们输入一个字符,是不是直接就用这个字符对应的unicode编号进行存储呢?

例如:
汉字严的 Unicode 是十六进制编号4E25,转换成二进制数足足有15位(100111000100101),如果我们直接用这个编号存储在计算机上就会有一个问题,当我们读出来的时候,我们怎么知道他是用两个字节表示一个字符,而不是使用的ascii编码,表示两个字符,也可能表示(‘N%’)。所以我们需要一种编码方式可以使得从内存中读出结果,能知道是两个字节表示一个还是两个字符。这就是utf8编码方式。

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8 是 Unicode 的实现方式之一。

UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8 的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。

2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围(十六进制)UTF-8编码方式(二进制)
0000 0000-0000 007F0xxxxxxx
0000 0080-0000 07FF110xxxxx 10xxxxxx
0000 0800-0000 FFFF1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

这里要说明一下字符集,编码方式的概念。
ASCII既是一种编码集,代表着字符对应的编码,也表示一种编码方式,在计算机中通过ascii编码的方式存储字符。
unicode是一种编码集,代表着字符对应的编码。但是在字符却不是直接把unicode的编码存在计算机上,而是通过utf8这种编码方式存储在计算机上。

下面来看一下python中字符编码


s1是string object,s2 是 unicode object。

>>> s1='严'
>>> s2=u'严'

可以看到在不同对象中,存储的编码是不同的,string object存储的是"严"通过utf8进行编码的形式(因为我是linux系统,默认是utf8编码,如果是windows,可能会是其他编码如GBK等),而unicode object中存储的是"严"对应的unicode编码。

>>> s1
'\xe4\xb8\xa5'
>>> s2
u'\u4e25'

通过print 可以将两种字符都以汉字的形式输出出来。如何做到的,string object通过chardet.detect可以知道s1的编码类型,通过编码类型就可以找到对应的unicode编号,找到对应的汉字。而unicode object直接通过编码就可以找到汉字。

>>> chardet.detect(s1)
{'confidence': 0.505, 'encoding': 'utf-8'}
>>> print s1
严
>>> print s2
严

如果把s1和s2放在list里面,可以输出对应的编码,但是已经无法输出汉字了,这是因为l1和l2已经不是字符串了,print也无法输出汉字

>>> l1=[s1]
>>> l2=[s2]
>>> l1
['\xe4\xb8\xa5']
>>> l2
[u'\u4e25']
>>> print l1
['\xe4\xb8\xa5']
>>> print l2
[u'\u4e25']

如果用json.dumps或者str后呢,可以看到dumps之后list中的字符都变成了unicode编码,即原来用utf8编码记录的list会变为用unicode编码的;而str则不会改变编码方式。print效果一样。

>>> json.dumps(l1)
'["\\u4e25"]'
>>> json.dumps(l2)
'["\\u4e25"]'
>>> str(l1)
"['\\xe4\\xb8\\xa5']"
>>> str(l2)
"[u'\\u4e25']"
>>> print str(l1)
['\xe4\xb8\xa5']
>>> print str(l2)
[u'\u4e25']
>>> print json.dumps(l1)
["\u4e25"]
>>> print json.dumps(l2)
["\u4e25"]

如果要显示中文

>>> print json.dumps(l1, ensure_ascii=False)
["严"]
>>> print json.dumps(l2, ensure_ascii=False)
["严"]

字典也可以使用这种方法显示中文

>>> d1= {'a' : s1}
>>> d2= {'a' : s2}
>>> print d1
{'a': '\xe4\xb8\xa5'}
>>> print d2
{'a': u'\u4e25'}
>>> print json.dumps(d1)
{"a": "\u4e25"}
>>> print json.dumps(d2)
{"a": "\u4e25"}
>>> print json.dumps(d1,ensure_ascii=False)
{"a": "严"}
>>> print json.dumps(d2,ensure_ascii=False)
{"a": "严"}

备注:
有文章说json显示中文需要如下方式:

json.dumps(m,ensure_ascii=False).decode('utf8').encode('gb2312')

这是因为window平台默认的中文编码编码方式是GBK,所以,在使用json.dumps(m,ensure_ascii=False) 时,该函数执行的结果为字符按照utf8方式的编码,print时会把utf8的编码使用GBK进行解码,所以出现乱码。因此先要使用utf8解码,再使用GBK编码,这样在print时,使用默认的GBK解码方式就可以输出正确的中文了。

window平台:

# -*- coding: utf-8 -*-
m = {'a' : '你好'}

print m
=>{'a': '\xe4\xbd\xa0\xe5\xa5\xbd'}

print json.dumps(m)
=>{"a": "\u4f60\u597d"}

print json.dumps(m,ensure_ascii=False)
=>{"a": "浣犲ソ"}

print json.dumps(m,ensure_ascii=False).decode('utf8').encode('gb2312')
=>{"a": "你好"}

ujson与json 模块的对比

使用ujson 输出含有中文的json字符串,分别使用%s和format两种方法。

# -*- coding: utf-8 -*-
import ujson as json
#import json
js_m = '''{"a" : "木马行为 - 告警次数超过阈值", "b":"10.10.10.10"}'''

m = json.loads(js_m)

#print alertDict

#print json.dumps(m)
try:
    print "result:'%s' and '%s'" %(json.dumps(m['b']), json.dumps(m, ensure_ascii=False))
except Exception,e:
    print e
print type(json.dumps(m, ensure_ascii=False))
print "result:'{}' and '{}'".format(m['b'],json.dumps(m, ensure_ascii=False).encode('utf8'))
print "result:'{}' and '{}'".format(str(m['b']),json.dumps(m, ensure_ascii=False))
print "result:'{}' and '{}'".format(json.dumps(m['b']),json.dumps(m, ensure_ascii=False))

结果:

➜  ~ python utf2.py
result:'"10.10.10.10"' and '{"a":"木马行为 - 告警次数超过阈值","b":"10.10.10.10"}'
<type 'str'>
Traceback (most recent call last):
  File "utf2.py", line 16, in <module>
    print "result:'{}' and '{}'".format(m['b'],json.dumps(m, ensure_ascii=False).encode('utf8'))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 6: ordinal not in range(128)

format输出错误,因为json.dumps(m, ensure_ascii=False)的结果为str,可以使用chardet.detect查看他的编码格式,发现它是utf8的编码,所以这里不能在使用encode(‘utf8’)。
修改为:

# -*- coding: utf-8 -*-
import ujson as json
#import json
js_m = '''{"a" : "木马行为 - 告警次数超过阈值", "b":"10.10.10.10"}'''

m = json.loads(js_m)

#print alertDict

#print json.dumps(m)
try:
    print "result:'%s' and '%s'" %(json.dumps(m['b']), json.dumps(m, ensure_ascii=False))
except Exception,e:
    print e
print type(json.dumps(m, ensure_ascii=False))
print "result:'{}' and '{}'".format(m['b'],json.dumps(m, ensure_ascii=False))
print "result:'{}' and '{}'".format(str(m['b']),json.dumps(m, ensure_ascii=False))
print "result:'{}' and '{}'".format(json.dumps(m['b']),json.dumps(m, ensure_ascii=False))

结果:

➜  ~ python utf.py
result:'"10.10.10.10"' and '{"a":"木马行为 - 告警次数超过阈值","b":"10.10.10.10"}'
<type 'str'>
{'confidence': 0.99, 'encoding': 'utf-8'}
result:'10.10.10.10' and '{"a":"木马行为 - 告警次数超过阈值","b":"10.10.10.10"}'
result:'10.10.10.10' and '{"a":"木马行为 - 告警次数超过阈值","b":"10.10.10.10"}'
result:'"10.10.10.10"' and '{"a":"木马行为 - 告警次数超过阈值","b":"10.10.10.10"}'

如果%s格式化也用三种方式,会是什么效果:

# -*- coding: utf-8 -*-
import ujson as json
#import json
js_m = '''{"a" : "木马行为 - 告警次数超过阈值", "b":"10.10.10.10"}'''

m = json.loads(js_m)

#print alertDict

#print json.dumps(m)
print type(m['b'])
print type(json.dumps(m, ensure_ascii=False))
print "result:'%s'" %(m['b'])

print "result:'%s'" %((json.dumps(m, ensure_ascii=False)))
print "result:'%s' and '%s'" %(m['b'], unicode(json.dumps(m, ensure_ascii=False)))
print "result:'%s' and '%s'" %(str(m['b']), json.dumps(m, ensure_ascii=False))
print "result:'%s' and '%s'" %(json.dumps(m['b']), json.dumps(m, ensure_ascii=False))

结果:

➜  ~ python utf.py
<type 'unicode'>
<type 'str'>
result:'10.10.10.10'
result:'{"a":"木马行为 - 告警次数超过阈值","b":"10.10.10.10"}'
Traceback (most recent call last):
  File "utf.py", line 86, in <module>
    print "result:'%s' and '%s'" %(m['b'], json.dumps(m, ensure_ascii=False))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 6: ordinal not in range(128)

原因是:单独输出m[‘b’] 和 json.dumps(m, ensure_ascii=False) 都可以输出,但是一起就不行了。一个是unicode类型,一个是str类,utf8编码。使用%s这种方式无法使用同时输出两种类型的字符。必须同一位一种类型,要么是str,要么是unicode。

两种类型一起输出的例子:

s1=u"sssss"
s2="严"
print "ret:'%s' and '%s'" %(s1, s2)

结果:

➜  ~ python utf.py
Traceback (most recent call last):
  File "utf.py", line 92, in <module>
    print "ret:'%s' and '%s'" %(s1, s2)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

我们把str类型编程unicode类型就可以了,修改代码:

print "result:'%s' and '%s'" %(m['b'], unicode(json.dumps(m, ensure_ascii=False)))

修改为:

print "result:'%s' and '%s'" %(m['b'], unicode(json.dumps(m, ensure_ascii=False),'utf8'))

这样m[‘b’] 和unicode(json.dumps(m, ensure_ascii=False),‘utf8’) 都是 unicode类型了,或者都改为str类型,那就是str(m[‘b’])和json.dumps(m[‘b’])两种方式。

总结


  1. %s输出方式不能同时格式化输出不同类型的字符,必须同时都变为str类型或者unicode类型。
  2. foramt 的方式则不存在这样的问题,可以输出两种不同类型的字符。
  3. ujson 和json的区别是,dumps()函数返回值,ujson是str类型,而json返回的是unicode类型。
  4. chardet.detect可以检测str类型的编码方式,通过这个函数可以知道str编码类型,更系统平台有关,linux为utf8为主,而window为GBK为主。
  5. python列表,字典类型的变量输出中文,可以使用json.dumps(x, ensure_ascii=False)。json和ujson一样的。
  6. 某些情况下要出中文还需要编解码,例如windows平台,默认是GBK编码,使用json.dumps(x, ensure_ascii=False)返回的是unicode类型,需要先用decode(‘utf8’)解码,再用encode(‘GBK’)编码才能正确输出中文字符。

最后再写一个有意思的C程序:

#include<stdio.h>
#include<stdlib.h>

int main(int argc, const char * argv[]) {
    char p[] = {0xe4,0xb8,0xa5};
    printf("%s\n", p);
    return 0;
}

请问他的输出是什么?
答案是

[yeruoxi@SELKS ~]$ gcc test.c
[yeruoxi@SELKS ~]$ ./a.out
严

参考:
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

https://blog.csdn.net/u014431852/article/details/53058951

https://blog.csdn.net/ktb2007/article/details/3876436

http://outofmemory.cn/code-snippet/4092/python-json-charset-type

http://python.jobbole.com/81244/

http://www.10tiao.com/html/331/201610/2651688148/1.html

https://blog.csdn.net/anlian523/article/details/80504699

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值