他盯着覆盖了龙眼的那层坚硬的瞬膜,想到那对在黑暗里缓缓睁开的黄金瞳,仿佛世界之门在他的眼前开启。

编码简介

ASCII

字母A是65,c是99,~是126等等, ASCII码就这样诞生了。原始的ASCII标准定义了从0到127 的字符,这样正好能用127个字节表示。

ISO-8859

ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。

GB2312

为了满足中文,中国人定制了GB2312,兼容ASCII

GBK

为了满足了更多的中文,GBK诞生,GBK:2Bytes代表一个字符;为了满足其他国家,各个国家纷纷定制了自己的编码。日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里,兼容ASCII,gb2312

GB18030

为了再满足少数民族的文字,GB18030编码覆盖中、日、朝鲜、少数民族文字,采用单字节、双字节、四字节三种字符编码,维文即使中过少数名字文字又是四字节编码,gb18030没理由不支持转码,经测试发现假设正确。

cp936

微软的CP936通常被视为等同GBK,连 IANA 也以“CP936”为“GBK”之别名。事实上比较起来, GBK 定义之字符较 CP936 多出95字(15个非汉字及80个汉字)。

万国码Unicode

有人开始觉得太多编码导致世界变得过于复杂了,让人脑袋疼,于是大家坐在一起拍脑袋想出来一个方法:所有语言的字符都用同一种字符集来表示,这就是Unicode。

Unicode统一用2Bytes代表一个字符,2的16次方-1=65535,可代表6万多个字符,因而兼容万国语言.

UTF-8

Unicode编码对于通篇都是英文的文本来说,这种编码方式无疑是多了一倍的存储空间(英文字母只需要一个字节就足够,用两个字节来表示,无疑是浪费空间)。于是产生了UTF-8:

UTF-8:对英文字符只用1Bytes表示,对中文字符用3Bytes

在UTF-8中,0-127号的字符用1个字节来表示,使用和US-ASCII相同的编码。这意味着1980年代写的文档用UTF-8打开一点问题都没有。只有128号及以上的字符才用2个,3个或者4个字节来表示。因此,UTF-8被称作可变长度编码。

总结

unicode:简单粗暴,所有字符都是2Bytes,优点是字符----->数字的转换速度快,缺点是占用空间大。

utf-8:精准,对不同的字符用不同的长度表示,优点是节省空间,缺点是:字符->数字的转换速度慢,因为每次都需要计算出字符需要多长的Bytes才能够准确表示。

因此,内存中使用的编码是unicode,用空间换时间(程序都需要加载到内存才能运行,因而内存应该是尽可能的保证快);硬盘中或者网络传输用utf-8,网络I/O延迟或磁盘I/O延迟要远大与utf-8的转换延迟,而且I/O应该是尽可能地节省带宽,保证数据传输的稳定性。而把数据存放到硬盘,或者网络传输,都需要把unicode转成utf-8,因为数据的传输,追求的是稳定,高效,数据量越小数据传输就越靠谱,于是都转成utf-8格式的,而不是unicode。

终端编码

windows下终端指的是CMD控制台,在控制台上输入输出有着其本身的编码格式,如windows控制台输入输出编码都为cp936。

在Linux下是utf-8。

为啥要了解终端编码?

因为你的python程序最终会在CMD下输出运行打印的结果,比如:CMD python xxx.py

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

Python中编码问题

上面说到内存中使用的编码是unicode,在Python2或者3中运行程序的时候内存中的文字编码都是Unicode。

但是运行中的编码是一回事,文件保存的编码又是一回事。上面提起,存储文本数据的时候如果使用Unicode编码会占用大量的内存,如果存储的时候把文本数据转换成utf-8编码格式来保存,然后取出放到内存的时候编码成Unicode岂不美滋滋?

的确是这么做的,你在win下保存txt文本的时候,在保存为下面有个选择框,让你选择存储编码。在python2中也是这样的,保存python2文件需要在开头加上一行

# condig:utf-8

这就是申明这个文件保存的编码格式为utf-8,如果不加上这一行的话,你在程序中运行中文就会出错,但是python3解决了这个问题。python3中默认的文件编码格式就是utf-8,所以不用加上这一行。

主要说一下decode和encode这两个方法,decode(‘gbk’)的意思就是把gbk编码的字符串转换成unicode编码的字符串,也就是个人理解的编码(世人皆认为这是解码),encode(‘utf-8’)的意思就是把unicode编码的字符串转换成utf-8编码,也就是个人理解的解码(世人皆认为这是编码),不过不打紧,你只需要明白他们的作用即可。

并且py中的数据字符串类型都有两种,下面对py字符串编码做下详细区分

python2

在python2中有两种字符串类型str和unicode,在python2里,str其实就是bytes

代码演示str

C:\Users\Administrator\Desktop>python
Python 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> s = '你好'
>>> s
'\xc4\xe3\xba\xc3'
>>> print s
你好

直接调用变量s,看到的却是一个个的16进制表示的二进制字节,其实就是bytes类型,即字节类型, 它把8个二进制一组称为一个byte,用16进制来表示。  

代码演示unicode

>>> s = u'你好'
>>> s
u'\u4f60\u597d'
>>> print s
你好

Unicode编码是一个反斜杠加上一个字母4,然后拼接4个字符。前面说到内存中使用的编码是unicode,如何让所有输入的字符串都变成Unicode编码呢?同样是上文提到的decode方法。

代码演示编码转换

>>> s = '你好'
>>> s
'\xc4\xe3\xba\xc3'
# 这里编码是str
>>> s.decode('utf-8')
# 尝试把s的内容以utf-8编码格式转出Unicode
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "F:\Python27\lib\encodings\utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xc4 in position 0: invalid continuation byte
>>> s.decode('gbk')
# 尝试把s的内容以gbk编码格式转出Unicode
u'\u4f60\u597d'
>>> s.decode()
# 尝试把s的内容以默认ascii编码格式转出Unicode
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc4 in position 0: ordinal not in range(128)
>>> s.decode('gb2312')
# 尝试把s的内容以gb2312编码格式转出Unicode
u'\u4f60\u597d'

只要是Unicode编码格式的字符串,打印出来都可以正常显示。

其他技巧

我们知道,python的字符串在内存中是unicode编码,存储的时候是utf-8编码保存。

在将unicode存储到文本的过程中,还有一种存储方式,不需要将unicode转换为实际的文本存储字符集,而是将unicode的内存编码值进行存储,读取文件的时候再反向转换回来,是采用:unicode-escape的转换方式。

将unicode编码的内容转化为中文(注意是内容,而不是字符串变量)

a="\\u8fdd\\u6cd5\\u8fdd\u89c4" #变量a的内容为unicode编码,变量a为string编码(""前不要加u)
b=a.decode('unicode-escape')
print b

对于utf-8编码的字符串,在存储的时候,通常是直接存储,而实际上也还有一种存储utf-8编码值的方法,即:string-escape。

将string编码的内容转化为中文(注意是内容,而不是字符串变量)

a="\\xe5\\x85\\xb3\\xe4\\xba\\x8e\\xe4" #变量a的内容为string编码,变量a为string编码(""前不要加u)
b=a.decode('string-escape')
print b

unicode-escape与utf-8的区别

>>>a="\u4e0a\u4f20\u6210\u529f"
>>>b=a.decode('utf-8')
>>>print type(b)
<type 'unicode'>
>>>b
u'\\u4e0a\\u4f20\\u6210\\u529f'
>>>print b
\u4e0a\u4f20\u6210\u529f

当对变量a做decode(‘utf-8’)时,除了对把变量a的类型从str变成了unicode,a变量的内容也做了utf-8解码,所以多了一些斜杠。

>>>a="\u4e0a\u4f20\u6210\u529f"
>>>c=a.decode("unicode-escape")
>>>print type(c)
<type 'unicode'>
>>>c
u'\u4e0a\u4f20\u6210\u529f'
>>>print c
上传成功

而对变量a做decode(‘unicode-escape’)时,貌似只有变量本身被decode成unicode了,其内容没有发生改变。

我们知道print函数会将变量以及变量内容都encode成str,因此第二个例子能输出中文,而第一个例子输出的还是unicode类型的内容,只不过少了一些斜杠,因为它还需要再encode一次。

当然本例子的转化,有更简单的方法,如下:

>>> d=u"\u4e0a\u4f20\u6210\u529f"  #定义变量d时,前面加个u,将其变成unicode
>>> print d
上传成功

列表中打印变成中文

一般来说字典或者列表中的中文字符串直接打印出来的话,会是unicode或者别的编码格式,使用如下方法解决

#coding:utf-8
d = {'user':'浪子',
     'password':'123456'}
print d

>>>{'password': '123456', 'user': '\xe6\xb5\xaa\xe5\xad\x90'}

dd = ['中文','123456']
print str(dd)
>>>['\xe4\xb8\xad\xe6\x96\x87', '123456']

这个dd列表中的中文是字符串编码,如果想打印的时候直接打印原始中文,可以这么做

print str(dd).decode("string-escape")#

如果dd列表中的中文是unicode编码,就是在前面加了一个字母u。

dd = [u'中文','123456']
print str(dd).decode("unicode-escape")
>>>['中文', '123456']

这样打印出来的就是中文

注意:

str表示字符的原始8位值,unicode表示Unicode字符。

在python2中,如果str只包含7位ASCII字符(英文字符),那么unicode与str实例类似于同一种类型(等价的),那么在这种情况下,以下几种操作是正常的:

  • 可以用+号连接str与unicode
  • 可以用=与!=来判断str与unicode
  • 可以用’%s’来表示Unicode实例

python3

python3把字符串变成了unicode,文件默认编码变成了utf-8,这意味着,只要用python3,无论你的程序是以哪种编码开发的,都可以在全球各国电脑上正常显示

在python3中有两种字符串类型str和bytes,并且对str 和bytes 做了明确区分, str 就是unicode格式的字符, bytes就是单纯二进制

代码演示str

C:\Users\Administrator\Desktop>python3
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> s = '你好'
>>> s
'你好'
>>> print(s)
你好
>>> type(s)
<class 'str'>
>>> s.decode('gbk')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'decode'
>>> s.encode('gbk')
b'\xc4\xe3\xba\xc3'
>>> s = u'你好'
>>> s
'你好'
>>> print(s)
你好
>>> type(s)
<class 'str'>

代码演示bytes

>>> s = '你好'
>>> s.encode('utf-8')
b'\xe4\xbd\xa0\xe5\xa5\xbd'
>>> s.encode('gbk')
b'\xc4\xe3\xba\xc3'
>>> print(type(s.encode('gbk')))
<class 'bytes'>

注意:

bytes表示字符的原始8位值,str表示Unicode字符。将unicode字符表示为二进制数据(原始8位值),最常见的编码方式就是UTF-8。python2与3中的unicode字符没有和特定的二进制编码相关联,因此需要使用encode方法。

在python3中bytes与str是绝对不会等价的,即使字符内容为””,因此在传入字符序列时必须注意其类型。

'''
对于bytes编码的数据来说
只有decode方法
就是把bytes编码的数据转换成utf-8编码

对于utf-8编码的数据来说
只有encode方法
就是把utf-8编码的数据转换成bytes编码
'''
a = '浪子'
print(a.encode())
# 默认是编码成bytes
print(a.encode('gbk'))
# 将utf-8编码格式转换成gbk编码格式
print(a.encode().decode('utf-8'))
print(a.encode('gbk').decode('gbk'))
# 相同编码格式必须要用相同编码才能解码

打印乱码常规解决办法

如果不想在打印输出的时候每个都加上decode的话,这两个方法可以帮到你

  1. 在开头加上这三行代码(仅Python2)

    import sys
    reload(sys)
    sys.setdefaultencoding(‘utf-8’)

  2. 在开头加上这两行代码

    import sys,io
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding=’gb18030’) #改变标准输出的默认编码

Chardet库

chardet是Python社区提供了一个类库包,方便我们在代码中动态检测当前页面或者文件中的编码格式信息。

import chardet
f = open('file','r')
fencoding=chardet.detect(f.read())
print fencoding

fencoding输出格式 {'confidence': 0.96630842899499614, 'encoding': 'GB2312'} 

只能判断是否为某种编码的概率。比较准确的结果了。输入参数为str类型。

一个实例,判断网页编码:

url = 'https://stackoverflow.com'
d1 = requests.get(url)
print d1.content
if isinstance(d1.content,unicode):
    pass
else:
    codesty = chardet.detect(d1.content)
    a = d1.content.decode(codesty['encoding'])

常见编码错误的原因有:

  1. Python解释器的默认编码
  2. Python源文件文件编码
  3. Terminal使用的编码
  4. 操作系统的语言设置

文件读写

Python 文件读写

中文iso8859-1编码转utf8编码

在项目中使用wget 进行ftp下载文件时,由于ftp下载默认的是ascii模式,下载的文件编码是iso8859-1。

在python3中直接使用open函数的话,需要设置编码,不然会报错。

open("08M0063639_20170710.txt","r",encoding='iso8859-1')

这样做,假如文本是数字或者字母的时候,没有什么影响。但文本是中文的时候,获取的数据是iso8859-1编码的,进行数据操作时会出现乱码。
所以要将数据转化为utf8编码

uft_str = str.encode("iso-8859-1").decode('gbk').encode('utf8')

先将文本转化成gbk编码,然后在从gbk编码变成utf8编码。
原理:

utf8编码的文本可以用iso8859-1的编码表示,但是反过来不行。iso8859-1是单字节编码,而utf8是定长编码,从utf8转化成iso8859-1相当于是高精度转化成低精度,造成精度丢失,所以不可逆。根本原因是因为utf8中文,在iso8859-1没有匹配的位置。

而gbk是不定长编码,英文数字的字符编码规则跟iso8859-1是一样的,所以gbk是兼容iso8859-1编码的,这两者可以相互转换。

1

2

3

4