Python 编码的前世今生

本文导航

-ASCII

-EASCII (ISO/8859-1)

-GBK

-Unicode

-UTF-8

-Python 字符编码

-str 与 unicode 的转换

-str(s) 与 unicode(s)

-乱码

-其他技巧

这是我在知乎上回答的一个问题:

Python 编码为什么那么蛋疼?

[1],期间收到了不少赞,不过发现我的回答还存在一些误导,于是通过查找资料重新整理了一篇,希望能解答你对编码的困惑。

一旦走上了编程之路,如果你不把编码问题搞清楚,那么它将像幽灵一般纠缠你整个职业生涯,各种灵异事件会接踵而来,挥之不去。只有充分发挥程序员死磕到底的精神你才有可能彻底摆脱编码问题带来的烦恼。

我第一次遇到编码问题是写 JavaWeb 相关的项目,一串字符从浏览器游离到应用程序代码中,翻江倒海沉浸到数据库中,随时随地都有可能踩到编码的地雷。

第二次遇到编码问题就是学 Python 的时候,在爬取网页数据时,编码问题又出现了,当时我的心情是崩溃的,用时下最ing的一句话就是:“我当时就懵逼了”。

为了搞清字符编码,我们得从计算机的起源开始,计算机中的所有数据,不论是文字、图片、视频、还是音频文件,本质上最终都是按照类似 01010101 的数字形式存储的。我们是幸运的,我们也是不幸的,幸运的是时代赋予了我们都有机会接触计算机,不幸的是,计算机不是我们国人发明的,所以计算机的标准得按美帝国人的习惯来设计,那么最开始计算机是通过什么样的方式来表现字符的呢?这要从计算机编码的发展史说起。

ASCII

每个做 JavaWeb 开发的新手都会遇到乱码问题,每个做 Python 爬虫的新手都会遇到编码问题,为什么编码问题那么蛋疼呢?

这个问题要从1992年 Guido van Rossum 创造 Python 这门语言说起,那时的 Guido 绝对没想到的是 Python 这门语言在今天会如此受大家欢迎,也不会想到计算机发展速度会如此惊人。Guido 在当初设计这门语言时是不需要关心编码的,因为在英语世界里,字符的个数非常有限,26个字母(大小写)、10个数字、标点符号、控制符,也就是键盘上所有的键所对应的字符加起来也不过是一百多个字符而已。这在计算机中用一个字节的存储空间来表示一个字符是绰绰有余的,因为一个字节相当于8个比特位,8个比特位可以表示256个符号。于是聪明的美国人就制定了一套字符编码的标准叫 ASCII(American Standard Code for Information Interchange),每个字符都对应唯一的一个数字,比如字符A对应的二进制数值是01000001,对应的十进制就是 65。最开始 ASCII 只定义了 128 个字符编码,包括 96 个文字和 32 个控制符号,一共 128 个字符,只需要一个字节的 7 位就能表示所有的字符,因此 ASCII 只使用了一个字节的后7位,最高位都为 0。每个字符与ASCII码的对应关系可查看网站ascii-code[2]。

EASCII (ISO/8859-1)  然而计算机慢慢地普及到其他西欧地区时,他们发现还有很多西欧所特有的字符是 ASCII 编码表中没有的,于是后来出现了可扩展的 ASCII 叫 EASCII ,顾名思义,它是在 ASCII 的基础上扩展而来,把原来的 7 位扩充到 8 位,它完全兼容 ASCII,扩展出来的符号包括表格符号、计算符号、希腊字母和特殊的拉丁符号。然而 EASCII 时代是一个混乱的时代,大家没有统一标准,他们各自把最高位按照自己的标准实现了自己的一套字符编码标准,比较著名的就有 CP437, CP437 是 Windows 系统中使用的字符编码,如下图:

cp437

另外一种被广泛使用的 EASCII 还有 ISO/8859-1(Latin-1),它是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位元字符集的标准,ISO/8859-1 只继承了 CP437 字符编码的 128-159 之间的字符,所以它是从 160 开始定义的,不幸的是这些众多的 ASCII 扩充字集之间互不兼容。

iso8859-1

GBK  随着时代的进步,计算机开始普及到千家万户,比尔盖茨让每个人桌面都有一台电脑的梦想得以实现。但是计算机进入中国不得不面临的一个问题就是字符编码,虽然咱们国家的汉字是人类使用频率最多的文字,汉字博大精深,常见的汉字就有成千上万,这已经大大超出了 ASCII 编码所能表示的字符范围了,即使是 EASCII 也显得杯水车薪,于是聪明的中国人自己弄了一套编码叫 GB2312,又称GB0,1981由中国国家标准总局发布。GB2312 编码共收录了6763个汉字,同时它还兼容 ASCII。GB2312 的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆 99.75% 的使用频率。不过 GB2312 还是不能 100% 满足中国汉字的需求,对一些罕见的字和繁体字 GB2312 没法处理,后来就在 GB2312 的基础上创建了一种叫 GBK 的编码。GBK 不仅收录了 27484 个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。同样 GBK 也是兼容 ASCII 编码的,对于英文字符用 1 个字节来表示,汉字用两个字节来标识。

Unicode

对于如何处理中国人自己的文字我们可以另立山头,按照我们自己的需求制定一套编码规范,但是计算机不止是美国人和中国人用啊,还有欧洲、亚洲其他国家的文字诸如日文、韩文全世界各地的文字加起来估计也有好几十万,这已经大大超出了 ASCII 码甚至 GBK 所能表示的范围了,况且人家为什么用采用你 GBK 标准呢?如此庞大的字符库究竟用什么方式来表示好呢?于是统一联盟国际组织提出了 Unicode 编码,Unicode 的学名是“Universal Multiple-Octet Coded Character Set”,简称为UCS。

Unicode 有两种格式:UCS-2 和 UCS-4。UCS-2 就是用两个字节编码,一共 16 个比特位,这样理论上最多可以表示 65536个字符,不过要表示全世界所有的字符显然 65536 个数字还远远不够,因为光汉字就有近 10 万个,因此 Unicode 4.0 规范定义了一组附加的字符编码,UCS-4 就是用 4 个字节(实际上只用了 31 位,最高位必须为 0)。

Unicode 理论上完全可以涵盖一切语言所用的符号。世界上任何一个字符都可以用一个 Unicode 编码来表示,一旦字符的 Unicode 编码确定下来后,就不会再改变了。但是 Unicode 有一定的局限性,一个 Unicode 字符在网络上传输或者最终存储起来的时候,并不见得每个字符都需要两个字节,比如一字符“A“,用一个字节就可以表示的字符,偏偏还要用两个字节,显然太浪费空间了。第二问题是,一个 Unicode 字符保存到计算机里面时就是一串 01 数字,那么计算机怎么知道一个 2 字节的 Unicode 字符是表示一个 2 字节的字符呢,还是表示两个 1 字节的字符呢,如果你不事先告诉计算机,那么计算机也会懵逼了。Unicode 只是规定如何编码,并没有规定如何传输、保存这个编码。例如“汉”字的 Unicode 编码是6C49,我可以用 4 个 ASCII 数字来传输、保存这个编码;也可以用 UTF-8 编码的 3 个连续的字节 E6 B1 89来表示它。关键在于通信双方都要认可。因此 Unicode 编码有不同的实现方式,比如:UTF-8、UTF-16 等等。这里的 Unicode 就像英语一样,做为国与国之间交流世界通用的标准,每个国家有自己的语言,他们把标准的英文文档翻译成自己国家的文字,这是实现方式,就像 UTF-8。

UTF-8

UTF-8(Unicode Transformation Format)作为 Unicode 的一种实现方式,广泛应用于互联网,它是一种变长的字符编码,可以根据具体情况用 1-4 个字节来表示一个字符。比如英文字符这些原本就可以用 ASCII 码表示的字符用 UTF-8 表示时就只需要一个字节的空间,和 ASCII 是一样的。对于多字节(n 个字节)的字符,第一个字节的前 n 为都设为 1,第 n+1 位设为 0,后面字节的前两位都设为 10。剩下的二进制位全部用该字符的 UNICODE 码填充。

以汉字“好”为例,“好”对应的 Unicode 是 597D,对应的区间是 0000 0800--0000 FFFF,因此它用 UTF-8 表示时需要用 3 个字节来存储,597D 用二进制表示是: 0101100101111101,填充到 1110xxxx 10xxxxxx 10xxxxxx 得到11100101 10100101 10111101,转换成 16 进制:E5A5BD,因此“好”的 Unicode “597D”对应的 UTF-8 编码是“E5A5BD”。

中文        好

unicode         0101   100101   111101

编码规则     1110xxxx 10xxxxxx 10xxxxxx

--------------------------

utf-8       11100101 10100101 10111101

--------------------------

16进制utf-8     e   5    a   5    b   d

Python 字符编码

现在总算把理论说完了。再来说说 Python 中的编码问题。Python 的诞生时间比 Unicode 要早很多,Python 的默认编码是ASCII。

>>> import sys

>>> sys.getdefaultencoding()

‘ascii‘

所以在 Python 源代码文件中如果不显式地指定编码的话,将出现语法错误

#test.py

print ‘你好‘

上面是 test.py 脚本,运行 python test.py 就会包如下错误:

File “test.py”, line 1 yntaxError: Non-ASCII character ‘\xe4′ in file test.py on line 1, but no encoding declared; see http://www.python.org/ ps/pep-0263.html for details

为了在源代码中支持非 ASCII 字符,必须在源文件的第一行或者第二行显示地指定编码格式:

# coding=utf-8

或者是:

#!/usr/bin/python

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

在 Python 中和字符串相关的数据类型,分别是 str、unicode 两种,他们都是 basestring 的子类,可见 str 与 unicode 是两种不同类型的字符串对象。

basestring

/  \

/    \

str    unicode

对于同一个汉字“好”,用 str 表示时,它对应的就是 UTF-8 编码‘\xe5\xa5\xbd‘,而用 Unicode 表示时,它对应的符号就是 u‘\u597d‘,与u‘好‘是等同的。需要补充一点的是,str 类型的字符其具体的编码格式是 UTF-8 还是 GBK,还是其它格式,根据操作系统相关。比如在 Windows 系统中,cmd 命令行中显示的:

# windows终端

>>> a = ‘好‘

>>> type(a)

type ‘str‘>

>>> a

‘\xba\xc3‘

而在 Linux 系统的命令行中显示的是:

# linux终端

>>> a=‘好‘

>>> type(a)  type ‘str‘>

>>> a  ‘\xe5\xa5\xbd‘

>>> b=u‘好‘

>>> type(b)  type ‘unicode‘>

>>> b  u‘\u597d‘

不论是 Python3x、Java 还是其他编程语言,Unicode 编码都成为了语言的默认编码格式,而数据最后保存到介质中的时候,不同的介质可有用不同的方式,有些人喜欢用 UTF-8,有些人喜欢用 GBK,这都无所谓,只要平台统一的编码规范,具体怎么实现并不关心。

encode

str 与 unicode 的转换

那么在 Python 中 str 和 unicode 之间是如何转换的呢?这两种类型的字符串类型之间的转换就是靠这两个方法:decode和encode。

py-encode

#从str类型转换到unicode

s.decode(encoding)   =====>  type ‘str‘> to type ‘unicode‘>

#从unicode转换到str

u.encode(encoding)  =====>  type ‘unicode‘> to type ‘str‘>

>>> c = b.encode(‘utf-8‘)

>>> type(c)

type ‘str‘>

>>> c

‘\xe5\xa5\xbd‘

>>> d = c.decode(‘utf-8‘)

>>> type(d)

type ‘unicode‘>

>>> d

u‘\u597d‘

这个‘\xe5\xa5\xbd‘就是 Unicode u‘好‘通过函数 encode 编码得到的 UTF-8 编码的 str 类型的字符串。反之亦然,str 类型的 c 通过函数 decode 解码成 Unicode 字符串 d。

str(s) 与 unicode(s)

str(s) 和 unicode(s) 是两个工厂方法,分别返回 str 字符串对象和 Unicode 字符串对象,str(s) 是s.encode(‘ascii’) 的简写。实验:

>>> s3 = u‘你好‘

>>> s3

u‘\u4f60\u597d‘

>>> str(s3)

Traceback (most recent call last):

File ‘‘, line 1, in module>

UnicodeEncodeError: ‘ascii‘ codec can‘t encode characters in position 0-1: ordinal not in range(128)

上面 s3 是 Unicode 类型的字符串,str(s3) 相当于是执行 s3.encode(‘ascii’),因为“你好”两个汉字不能用 ASCII 码来表示,所以就报错了,指定正确的编码:s3.encode(‘gbk‘) 或者 s3.encode(‘utf-8‘) 就不会出现这个问题了。类似的 Unicode 有同样的错误:

>>> s4 = ‘你好‘

>>> unicode(s4)

Traceback (most recent call last):

File ‘‘, line 1, in module>

UnicodeDecodeError: ‘ascii‘ codec can‘t decode byte 0xc4 in position 0: ordinal not in range(128)  >>>

unicode(s4) 等效于 s4.decode(‘ascii‘),因此要正确的转换就要正确指定其编码 s4.decode(‘gbk‘) 或者s4.decode(‘utf-8‘)。

乱码

所有出现乱码的原因都可以归结为字符经过不同编码解码在编码的过程中使用的编码格式不一致,比如:

# encoding: utf-8

>>> a=‘好‘

>>> a  ‘\xe5\xa5\xbd‘

>>> b=a.decode(‘utf-8‘)

>>> b  u‘\u597d‘

>>> c=b.encode(‘gbk‘)

>>> c  ‘\xba\xc3‘

>>> print c

??

UTF-8 编码的字符‘好’占用 3 个字节,解码成 Unicode 后,如果再用 GBK 来解码后,只有 2 个字节的长度了,最后出现了乱码的问题,因此防止乱码的最好方式就是始终坚持使用同一种编码格式对字符进行编码和解码操作。

decode-encode

其他技巧

对于如 Unicode 形式的字符串(str 类型):

s = ‘id\u003d215903184\u0026index\u003d0\u0026st\u003d52\u0026sid‘

转换成真正的 Unicode 需要使用:

s.decode(‘unicode-escape‘)

测试:

>>> s = ‘id\u003d215903184\u0026index\u003d0\u0026st\u003d52\u0026sid\u003d95000\u0026i‘

>>> print(type(s))  type ‘str‘>

>>> s = s.decode(‘unicode-escape‘)

>>> s  u‘id=215903184&index=0&st=52&sid=95000&i‘

>>> print(type(s))

type ‘unicode‘>

>>>

以上代码和概念都是基于 Python2.x。

参考:  https://www.python.org/dev/peps/pep-0263/  http://www.liaoxuefengcom/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819196283586a37629844456ca7e5a7faa9b94ee8000  http://www.fmddlmyy.cn/text6.html

转载自: http://foofish.net/blog/111/python-character-encode  转载自: http://foofish.net/blog/111/python-character-encode  作者: _zhijun

时间: 2024-09-30 12:15:01

Python 编码的前世今生的相关文章

【转】python编码的问题

摘要: 为了在源代码中支持非ASCII字符,必须在源文件的第一行或者第二行显示地指定编码格式: # coding=utf-8 或者是: #!/usr/bin/python # -*- coding: utf-8 -*- 在python中和字符串相关的数据类型,分别是str.unicode两种,他们都是basestring的子类,可见str与unicode是两种不同类型的字符串对象. basestring / \ / str unicode 不论是Python3x.Java还是其他编程语言,Uni

PYTHON编码处理-str与Unicode的区别

一篇关于str和Unicode的好文章 整理下python编码相关的内容 注意: 以下讨论为Python2.x版本, Py3k的待尝试 开始 用python处理中文时,读取文件或消息,http参数等等 一运行,发现乱码(字符串处理,读写文件,print) 然后,大多数人的做法是,调用encode/decode进行调试,并没有明确思考为何出现乱码 所以调试时最常出现的错误 错误1 Traceback (most recent call last): File "<stdin>"

Python编码规则

1. 命名规则 1.1 变量名.包名.模块名 变量名通常有字母.数字和下划线组成,且首字母必须是字母或下划线,并且不能使用python的保留字:包名.模块名通常用小写字母 1.2 类名.对象名 类名首字母用大写,其他字母采用小写:对象名用小写字母.类的属性和方法名以对象作为前缀,对象通过操作符"."访问属性和方法.类的私有变量.私有方法以两个下划线作为前缀. l.3 函数名     函数名通常采用小写,并用下划线或单词首字母大写来增加名称的可读性,导入的函数以模块名作为前缀. 2. 模

Python 编码

Python 编码 ASCII.Unicode.UTF-8 以及 gbk 在具体说明 Python 编码之前,先来理清 ASCII.Unicode.UTF-8.gbk 究竟是什么? 这边仅简单介绍下,具体请百度. ASCII:是现今最通用的单字节编码系统.ASCII(仅1~127) 仅可代表英文.数字及一些符号等,如,A 的 ASCII 码为65(十进制). Unicode:为了解决传统的字符编码方案的局限而产生,为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言.跨平台进行文本

说说Python编码规范

前言 已有近两个月没有发表过文章了,前段时间外甥和女儿过来这边渡暑假,平常晚上和周末时间都陪着她们了,趁这个周末有空,再抽空再把这块拾起来.         这么久没写了,再次拿起键盘,想想,发表些什么呢,想起上次公司的代码评审委员会下周其中一个议题是关于Python编码规范的整理,那就趁热打铁,整理一份关于Python编码规范的文章,也为那些写Python的人,提供一些编码注意的一些事项或者说是参考吧. 编码规范的作用         规范故明思义,就是通过不断的总结,吸取好的点,从而形成的一

python 编码问题:&#39;ascii&#39; codec can&#39;t encode characters in position 的解决方案

问题描述: Python在安装时,默认的编码是ascii,当程序中出现非ascii编码时,python的处理常常会报这样的错UnicodeDecodeError: 'ascii' codec can't decode byte 0x?? in position 1: ordinal not in range(128),python没办法处理非ascii编码的,此时需要自己设置将python的默认编码,一般设置为utf8的编码格式. 查询系统默认编码可以在解释器中输入以下命令: Python代码

Python Solve UnicodeEncodeError &#39;gbk&#39; / &#39;ascii&#39; / &#39;utf8&#39; codec can&#39;t encode character &#39;\x??&#39; in position ? 解决有关Python编码的错误

在Python中,处理中文字符一直是很令人头痛的问题,一言不合就乱码,而且引起乱码的原因也不尽相同,有时候是python本身默认的编码器设置的不对,有时候是使用的IDE的解码器不对,还有的时候是终端terminal的解码器不对,有时候同一份代码在Python2上正常运行,Python3上就不行了,反正产生乱码的原因很多,这里就列举一些博主遇到过的一些错误及其解决方案: Error 1: UnicodeEncodeError: 'gbk' codec can't encode character

PEP8 Python 编码规范

PEP8 Python 编码规范 一 代码编排 1 缩进.4个空格的缩进(编辑器都可以完成此功能),不使用Tap,更不能混合使用Tap和空格.2 每行最大长度79,换行可以使用反斜杠,最好使用圆括号.换行点要在操作符的后边敲回车.3 类和top-level函数定义之间空两行:类中的方法定义之间空一行:函数内逻辑无关段落之间空一行:其他地方尽量不要再空行. 二 文档编排 1 模块内容的顺序:模块说明和docstring-import-globals&constants-其他定义.其中import部

【Python进阶】02、python编码问题

一.ASCII.Unicode和UTF-8的区别 因为字符编码的问题而苦恼不已,于是阅读了大量的博客,再进行了一定的测试,基本搞清楚了编码问题的前因后果. 1.字符集和字符编码 计算机中储存的信息都是用二进制数表示的:而我们在屏幕上看到的英文.汉字等字符是二进制数转换之后的结果.通俗的说,按照何种规则将字符存储在计算机中,如'a'用什么表示,称为"编码":反之,将存储在计算机中的二进制数解析显示出来,称为"解码",如同密码学中的加密和解密.在解码过程中,如果使用了错