目录
2.5 unicode.encode 与 unicode.__str__的区别
1 字符编码
首先来说一说gbk,gb2312,unicode,utf-8这些术语,这些术语与语言无关。
计算机的世界只有0和1,因此任何字符(也就是实际的文字符号)也是由01串组成。计算机为了运算方便,都是8个bit组成一个字节(Byte),字符表达的最小单位就是字节,即一个字符占用一个或者多个字节。字符编码(character encoding)就是字集码,编码就是将字符集中的字符映射为一个唯一二进制的过程。
计算机发源于美国,使用的是英文字母(字符),所有26个字母的大小写加上数字0到10,加上符号和控制字符,总数也不多,用一个字节(8个bit)就能表示所有的字符,这就是ANSI的“Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。比如,小写字母‘a’的ascii 码是01100001,换算成十进制就是97,十六进制就是0x61。计算机中,一般都是用十六进制来描述字符编码。
但是当计算机传到中国的时候,ASCII编码就行不通了,汉字这么多,一个字节肯定表示不下啊,于是有了GB 2312(中国国家标准简体中文字符集)。GB2312使用两个字节来对一个字符进行编码,其中前面的一个字节(称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,GB2312能表示几千个汉字,而且与asill吗也是兼容的。
但后来发现,GB2312还是不够用,于是进行扩展,产生了GBK(即汉字内码扩展规范), GBK同Gb2312一样,两个字节表示一个字符,但区别在于,放宽了对低字节的要求,因此能表示的范围扩大到了20000多。后来,为了容纳少数名民族,以及其他汉字国家的文字,出现了GB13080。GB13080是兼容GBK与GB2312的,能容纳更多的字符,与GBK与GB2312不同的是,GB18030采用单字节、双字节和四字节三种方式对字符编码
因此,就我们关心的汉字而言,三种编码方式的表示范围是:
GB18030 》 GBK 》 GB2312
即GBK是GB2312的超集,GB1803又是GBK的超集。后面也会看到,一个汉字可以用GBK表示,但不一定能被GB2312所表示
当然,世界上还有更多的语言与文字,每种文字都有自己的一套编码规则,这样一旦跨国就会出现乱码,亟待一个全球统一的解决办法。这个时候ISO(国际标准化组织)出马了,发明了"Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “unicode”。目标很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号 的编码!
unicode每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。unicode编码一定以\u开头。
但是,unicode只是一个编码规范,是所有字符对应二进制的集合,而不是具体的编码规则。或者说,unicode是表现形式,而不是存储形式,就是说没用定义每个字符是如何以二进制的形式存储的。这个就跟GBK这些不一样,GBK是表里如下,表现形式即存储形式。
比如汉字“严”的unicode编码是\u4e25,对应的二进制是1001110 00100101,但是当其经过网络传输或者文件存储时,是没法知道怎么解析这些二进制的,容易和其他字节混在一起。那么怎么存储unicode呢,于是出现了UTF(UCS Transfer Format),这个是具体的编码规则,即UTF的表现形式与存储格式是一样的。
因此,可以说,GBK和UTF-8是同一个层面的东西,跟unicode是另一个层面的东西,unicode飘在空中,如果要落地,需要转换成utf-8或者GBK。只不过,转换成Utf-8,大家都能懂,更懂用,而转换成GBK,只有中国人才看得懂
UTF也有不同的实现,如UTF-8, UTF-16。
2 python2.7 中的字符编码问题
2.1 ascii, unicode, utf8
- ascii码:最早的编码,只有127个字符,包含英文字母,数字,标点符号和一些其它符号。一个字节(8 bits)表示一个字符。
- unicode(统一码):一个字节不够放,全世界有各种语言的字符需要编码,于是unicode给所有的字符都设定了唯一编码。通常都是用两个字节表示一个字符(有些生僻的字要用四个字节)。所以,要理解一点:下文中提到到的unicode编码是双字节编码(一个字符 两个字节)。
- uft8:对于ascii编码的那些字符,只需要1个字节,unicode给这些字符也设定2个字节,如果一篇文章全是英文(ascii字符),就浪费了很多空间(本来1个字节可以存储的,用了2个字节),所以产生了utf8。UTF-8是在Unicode基础上发展而来的可变长的编码方式,根据不同的符号变化字节长度,把ascii编码成1个字节,汉字通常编码成3个字节,一些生僻的字符编码成4~6个字节。
在计算机内存中,统一使用 Unicode编码 ,这种编码方式可以将世界上所有的可表示字符都赋予一个唯一的编码。
在python中,建议程序过程中统一使用unicode编码,保存文件和读取文件时使用utf-8(在读写磁盘文件时候用utf8进行相应的decode和encode,关于decode和encode见下文1.4 )。
Character | ASCII | Unicode | UTF-8 | GBK |
0 | 00110000 | 00000000 00110000 | 00110000 | 00110000 |
a | 01100001 | 00000000 01100001 | 01100001 | 01100001 |
字 | 无法表示 | 01011011 01010111 (u'\u5b57') | 11100101 10101101 10010111 ('\xe5\xad\x97') | 11010111 11010110 ('\xd7\xd6') |
注意:
- 上面的0是字符0,对应程序中“0”,而不是数字0。
- 数字是不使用字符编码的,数字可以使用原码、反码和补码表示,在内存中一般使用补码表示,其字节序有大小端模式决定。
要记住:所有的编码方式都是向后兼容ASCII的。ASCII字符对应什么二进制,那么在其他编码方式中也对应同样的二进制。
注:
能用到向后兼容和向前兼容, 那么肯定是存在接口概念的。 也就是说存在一个系统提供接口供外部使用, 外部应用使用这个接口。 然后就存在系统迭代周期和应用迭代周期不一致的问题,这时候就出现了向前兼容和向后兼容的说法。
- 向前兼容的英文为 Forwards Compatibility,Forward有“将来”的含义。因此向前兼容就是指:以前的版本支持现在版本生成的数据,现在的版本支持以后的版本数据。
(1)“Windows 3.1要能运行为Windows 10开发的程序”
(应用使用了一些新的接口,但是系统却是旧的系统)
(2)“Word 2003 能够打开用Word 2007创建的文件。”
(Office是系统,文件就是使用接口的应用。Office 2007的文件就是使用了新的接口 , Office 2003 就是旧的系统)
(3)“USB3.0的U盘,插在USB1.0的接口上”
- 向后兼容的英文为Backwards Compatibility,Backward有“回头”的意思。所以向后兼容就是指现在的版本可以支持以前的版本数据。
(1)“Windows 10要能运行为Windows 3.1开发的程序”
(在这里,Window是系统,开发的程序就是使用系统接口的应用。应用使用了旧的接口,系统是新的系统)
(2)“RFC2018的设备能够兼容RFC793协议”
(在这里,使用TCP协议的设备都是系统,但是存在一些设备使用了 RFC2018协议, 一些设备使用了 RFC93协议 。使用了RFC93协议的设备就是使用了旧的接口,使用了 RFC2018协议的设备就是新系统)
(3)“2007 Microsoft Office 系统能够打开 Office 2000和Office 2003的文件”
(在这里,Office是系统,文件就是使用接口的应用。Office 2000和Office 2003的文件就是使用了旧的接口 , Office 2007 就是新的系统)
(4)“CD盘可以放在CD光驱播放,也可以放在DVD光驱播放。”
(光驱是系统,CD盘是使用接口的应用。CD是旧接口, DVD光驱是新系统)
兼容性是形容系统的。
- 系统向后兼容 , 也就是兼容以前的 , 就说明系统是相对新的。
- 系统向前兼容 , 也就是兼容未来的 , 就说明系统是相对旧的。
2.2 encoding声明
python默认使用ascii编码去解释源文件。
import sys
sys.getdefaultencoding()
# 'ascii'
如果源文件中出现了非ASCII码字符,不在开头声明encoding会报错。
可以声明为utf8,告诉解释器用utf8去读取文件代码,这个时候源文件有中文也不会报错。
# encoding=utf8 如果不加这一行会报错
print '解释器用相应的encoding去解释python代码'
在文件开头加入# -*- coding: UTF-8 -*- 或者 # coding=utf-8 。
注意:# coding=utf-8 的 = 号两边不要空格。
2.3 python2.7中的str和unicode
debugger的时候会发现,python2中有两种表示字符串的类型:,unicode和str。basestring是二者的共同基类。他们有同一个基类basestring。str是plain string,其实应该称之为字节串,因为是每一个字节换一个单位长度。而unicode就是unicode string,这才是真正的字符串,一个字符(可能多个字节)算一个单位长度。同一个汉字,不同的类型其长度也是不一样的,对于unicode类型的实例,其长度一定是字符的个数,而对于str类型的实例,其长度是字符对应的字节数目。
str为字节码,会根据某种编码把字符串转成一个个字节,这个时候字符和字节没有所谓固定的一一对应的关系。
unicode则是用unicode编码的字符串,这个时候一个字符是对应两个字节的,一一对应。
Unicode对象包含的是unicode字符,而str对象包含的是字节(byte)。
另外强调的是,unicode类型一定是unicode编码,而str类型可能是gbk、ascii或者utf-8编码。
直接赋值字符串,类型为str,str为字节串,会按照开头的encoding(即 # encoding=utf8 等)来编码成一个个的字节。
赋值的时候在字符串前面加个u,类型则为unicode,直接按照unicode来编码。
同一个汉字,不同的类型其长度也是不一样的,
- unicode类型的实例,包含的是unicode字符,其长度一定是字符的个数,例如一个汉字的长度是1。
- str类型的实例,包含的是字节(byte),其长度是字符对应的字节数目。例如utf8的一个汉字长度是3。
默认使用ascii编码时:
s1 = '字节串'
print type(s1) #输出 <type 'str'>,按照开头的encoding来编码成相应的字节。
print len(s1) #输出9,因为按utf8编码,一个汉字占3个字节,3个字就占9个字节。
s2 = u'统一码'
print type(s2) #输出 <type 'unicode'>,用unicode编码,2个字节1个字符。
print len(s2) #输出3,unicode用字符个数来算长度,从这个角度上看,unicode才是真正意义上的字符串类型
比如我们要从一个文件中找出中所有后两位是'学习'的词语,再进行判断的时候:
# -*- coding: utf-8 -*-
s = '机器学习'
print(len(s))
print(s[-2:])
print('学习')
print(s[-2:] == '学习')
# 12
# (乱码)
# 学习
# False
# 返回false,平时写程序可能会以为相等。
# 这里的”学习是用开头的encoding声明解释的,开头用的是utf8,汉字占3个字节,所以“学习”占了6个字节),而s[-2:]取的是最后两个”双字节“,所以不相同。
s = u'机器学习'
print(len(s))
print(s[-2:])
print(u'学习')
print(s[-2:] == '学习')
# 4
# 学习
# 学习
# True
# 返回true,这也是为什么说unicode是真正意义上的字符串类型。因为使用的是unicode,”学习“占的是两个”双字节“,一个"双字节“一个字。
对于经常处理中文字符串的人,统一用unicode就可以避免这个坑了。
虽然有些字符串处理函数用str也可以,应该是函数里面帮你处理了编码问题。
注:
关于用re模块的正则查找方法search时总是找不出来(找错了或者出乱码)的问题。用utf8编码的str类型的字符串在search方法中行不通,因为str是字节串,和字符之间没有固定的一一对应的关系,正则没法用字节串来进行正确匹配。把正则式和目标字符串都使用unicode类型,unicode和字符之间是两个字节对应一个字符的关系,正则可以根据这个来对字符进行匹配。
__str__ __repr__的区别:
这是python中两个magic method,很容易让新手迷糊,因为很多时候,二者的实现是一样的,但是这两个函数是用在不同的地方
- _str__, 主要是用于展示,str(obj)或者print obj的时候调用,返回值一定是一个str 对象
- __repr__, 是被repr(obj), 或者在终端直接打obj的时候调用
>>> us = u'严'
>>> us
u'\u4e25'
>>> print us
严
可以看到,
- 不使用 print 返回的是一个更能反映对象本质的结果,即 us 是一个 unicode 对象(最前面的 u 表示,以及 unicode 编码是用的 \u),且“严”的 unicode 编码确实是 4E25。
- 而 print 调用可 us.__str__,等价于 print str(us),使得结果对用户更友好。
2.4 python2.7中的encode和decode
讲解编码和解码之前,先来讲讲Unicode和utf-8的关系。
可以这样来理解:字符串是由字符构成,字符在计算机硬件中通过二进制形式存储,这种二进制形式就是编码。如果直接使用 “字符串↔️字符↔️二进制表示(编码)” ,会增加不同类型编码之间转换的复杂性。所以引入了一个抽象层,“字符串↔️字符↔️与存储无关的表示↔️二进制表示(编码)” ,这样,可以用一种与存储无关的形式表示字符,不同的编码之间转换时可以先转换到这个抽象层,然后再转换为其他编码形式。在这里,unicode 就是 “与存储无关的表示”,utf—8 就是 “二进制表示”。
python2中字符串有两种表示形式,str和unicode。str可以理解为上面这段话中的二进制编码格式,unicode可以理解为抽象层。encode是编码,即从unicode格式到二进制的编码格式如utf-8、gb2312等。decode是解码,即从二进制编码格式到unicode编码格式。
- encode的正常使用:对unicode类型进行encode,得到字节串str类型。也即是unicode -> encode(根据指定编码) -> str。
- decode的正常使用:对str类型进行decode,得到unicode类型。也即是str -> decode(根据指定编码) -> unicode。
注意:encode和decode的时候都是需要指定编码的。
因为在编码的时候要知道原来的编码是什么和按照什么新编码方式进行编码,要用到两种编码,这里默认有一个unicode,所以需要再指定一个编码方式。解码的时候也是一个道理。
这两个方法就是在unicode和str之间用指定编码进行转换。
s3 = u'统一码'.encode('utf8')
print type(s3) # 输出 <type 'str'>
s4 = '字节串'.decode('utf8')
print type(s4) #输出 <type 'unicode'>
- encode的不正常使用:对str类型进行encode,因为encode需要的是unicode类型,这个时候python会用默认的系统编码decode成unicode类型,再用你给出编码进行encode。(注意这里的系统编码不是开头的encoding,具体例子见下文第5点)
- decode的不正常使用:对unicode类型进行decode,python会用默认的系统编码encode成str类型,再用你给出的编码进行decode。
所以改好对应的系统默认编码,就算不正常使用,也不会报错啦。不过多拐了一下路。
2.5 unicode.encode 与 unicode.__str__的区别
str.encode([encoding[, errors]])
Return an encoded version of the string. Default encoding is the current default string encoding.
object.__str__(self)
Called by the str() built-in function and by the print statement to compute the “informal” string representation of an object.
注意:str.encode 这里的 str 是 basestring,是 str 类型与 unicode 类型的基类。
可以看到encode方法是有可选的参数:encoding 和 errors,在上面的例子中encoding即为utf-8;而__str__是没有参数的,我们可以猜想,对于unicode类型,__str__函数一定也是使用了某种encoding来对unicode进行编码。
首先不禁要问,如果encode方法没有带入参数,是什么样子的:
>>> us.encode()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u4e25' in position 0: ordinal not in range(128)
不难看出,默认使用的就是ascii码来对unicode就行编码,为什么是ascii码,其实就是系统默认编码(sys.getdefaultencoding的返回值)。ascii码显然无法表示汉字,于是抛出了异常。而使用utf-8编码的时候,由于utf能够表示这个汉字,所以没报错。
如果直接打印ss(us.encode('utf-8')的返回值)会怎么样。
>>> print ss
涓
结果略有些奇怪,us.__str__(即直接打印us)的结果不一样,那么试试encoding = gbk呢?
>>> print us.encode('gbk')
严
事实上也是如此,python会采用终端默认的编码(用locale.getdefaultlocale()查看,windows是为gbk)将unicode编码成str类型。
在Linux(终端编码为utf-8),结果如下:
>>> us= u'严'
>>> print us.encode('utf-8')
严
>>> print us.encode('gbk')
▒▒
>>> print us
严
>>>
注意上面的乱码!
2.6 修改系统默认编码
系统默认使用ascii编码,需要进行相应的修改。
这个编码和开头的encoding不同之处在于,开头的encoding是对于文件内容的编码。
这里的编码是一些python方法中默认使用的编码,比如对str进行encode的时候默认先decode的编码,比如文件写操作write的encode的编码(关于文件读写见下文第7点)
import sys
reload(sys)
sys.setdefaultencoding('utf8')
s = '字节串str'
s.encode('utf8')
#等价于
s.decode(系统编码).encode('utf8')
关于系统默认编码发挥作用的地方,来看看另一个例子。
import sys
print sys.getdefaultencoding() # 输出ascii
s = 'u华南理工大学'
print s[-2:] == '大学' # 返回False,并有warning提醒
reload(sys)
sys.setdefaultencoding('utf8')
print s[-2:] == '大学' # 返回True
根据结果得知:python在用==比较时,如果第一个操作符是unicode而第二个不是的话,会自动用系统默认编码帮第二个操作符decode。
PS:为什么需要reload(sys)呢。首先,reload是用于重新加载之前import的模块。
这里需要重新加载sys的原因是:python在加载模块时候删除了sys中的setdefaultencoding方法(可能是出于安全起见),所以需要reload这个sys模块。
这里再举个简单例子,比如要修改keras的后端,从tensorflow改成theano,修改后需要重新加载keras的backend模块才能修改成功。
import keras.backend as K
k.backend() # 一开始是u'tensorflow'
import os
os.environ['KERAS_BACKEND'] = 'theano'
K.backend() # 修改后还是u'tensorflow'
reload(K)
k.backend() # reload之后后端才变成u'theano'
2.7 查看文件编码
import chardet
with open(filename,'r') as f:
data = f.read()
return chardet.detect(data)
2.8 文件读写
首先要记住,读出和写入,这两个文件的关口都是用str类型的,就是一个个字节。
2.8.1 内置的默认open
python中内置的默认open在读取文件的时候以字节串str的形式,读出一个个字节。读取后要用正确的编码才能decode成正确的unicode,所以要知道原来在文件中的编码。
写文件的时候也是一个道理,用str类型,以字节的形式写入,这个str是以某种编码方式编码的,要注意用正确的编码方式编码,一般是按utf8编码后写文件。
如果你用unicode类型写入,python会根据系统默认编码来把unicode编码成str再写入文件。因为写入文件需要的是str,是str就写,不是就把你转成str再写。
简单原则,尽量用str写入,避免使用默认编码,这样也不用在开头修改默认编码。
2.8.2 模块codecs中的open
python中模块codecs中的open方法可以指定一个编码。它保证了读入和写出的字节都是按照这个指定编码进行编码的。这样在读文件的时候:会把读出的str按照指定编码decode成unicode。
写文件的时候:如果是unicode,会根据指定编码encode成str然后写入;如果是str,会根据系统默认编码把str进行decode得到unicode,再根据指定编码encode成str进行写入。
简单原则,尽量用unicode写入,避免使用默认编码,这样也不用在开头修改默认编码。
PS:注意一下,对于其它方式读写文件,需要自行debugger看看编码的问题。比如在python中读取excel的时候读出来就直接是unicode而不是str。
2.9 一般的处理要点
(1) 首先把源文件的默认encoding和系统默认编码改为utf8
(2) 程序执行过程统一使用unicode类型
(3) 对于读写文件(用python内置的默认open来说),得到的是str,对str进行相应的encode和decode就可以了。
总结一下就是:
- 设置相应的默认编码为utf8;
- 读文件拿到str类型:str -> decode('utf8') -> unicode
- 程序处理:用unicode
- 写文件:unicode -> encode('utf8') -> str,用str类型写入文件
- 当然前提是文件都是utf8格式的啦,包括源文件和读写的数据文件。
另外想说一下:
对于写程序的过程中统一使用unicode类型这一点只是一个建议,因为统一unicode可以在处理字符串的时候减少麻烦。
觉得全部弄成unicode麻烦的,可以考虑平时统一用utf8编码的str,有些问题需要用unicode的再转为unicode,遇到编码问题时可以思考是不是没有统一用unicode的问题(本文开头就给出了一个需要统一用unicode的情况)
3 python3字符编码转换
3.1 源码文件默认编码
Python3.X 源码文件默认使用utf-8编码,所以可以正常解析中文,无需指定 UTF-8 编码。
注意:如果你使用编辑器,同时需要设置 py 文件存储的格式为 UTF-8,否则会出现类似以下错误信息:
SyntaxError: (unicode error) ‘utf-8’ codec can’t decode byte 0xc4 in position 0:
invalid continuation byte
3.2 Pycharm 设置步骤
- 进入 file > Settings,在输入框搜索 encoding。
- 找到 Editor > File encodings,将 IDE Encoding 和 Project Encoding 设置为utf-8。
3.3 字符编码转换
PYTHON编码类型默认是UTF-8,然而在工作中经常会遇到不同编码的问题,这时就需要对不同的编码进行转换,在python中不同编码的转换都要经过Unicode,不论是UTF-8或者是GBK,整个过程都是先通过编码decode转换为Unicode告诉Unicode当前的编码格式是什么、然后再通过解码encode转换为自己想要实现的编码格式或类型。
Python 3.7.3:
import sys
sys.getdefaultencoding()
# 'utf-8'
Unicode字符串可以用多种方式编码为普通字符串,假设unicodestring = u"Hello world",依照所选择的编码(encoding),如下:
1 #将Unicode转换成普通的Python字符串str:"编码(encode)"。
utf8string = unicodestring.encode("utf-8")
asciistring = unicodestring.encode("ascii")
isostring = unicodestring.encode("ISO-8859-1")
utf16string = unicodestring.encode("utf-16")
2 # 将普通Python字符串str转化为Unicode:"decode"
plainstring1 = unicode(utf8string, "utf-8")
plainstring2 = unicode(asciistring, "ascii")
plainstring3 = unicode(isostring, "ISO-8859-1")
plainstring4 = unicode(utf16string, "utf-16")
https://blog.csdn.net/qq_39198486/article/details/81608288
https://zhidao.baidu.com/question/1732357323587493987.html
https://blog.csdn.net/VictoriaW/article/details/75314737
一文搞懂Python2字符编码:https://www.cnblogs.com/xybaby/p/7814299.html#_label_5