编程笔记 | 字符编码、Python字符串以及常见异常
在讲 Python 字符串之前先了解一下常用的字符编码
常用三种字符编码发展
常用的三种字符编码与特点:
- ASCII码 :最早
- Unicode : 把所有语言都统一到一套编码
- UTF-8 : 相对于Unicode节省空间,并兼容ASCII码
发展由来:
最早的编码是「ASCII码」,其编码范围为 0 - 127,即只能存放127个字符,包括了大小写英文字母、数字和一些符号。每个字符占一个字节。
这样,ASCII 码只能处理英文,那么其他语言字符呢?
其他语言字符编码则是在 ASCII 码基础上扩展。例如,要处理一个中文字符一个字符是不够的,且不能与ASCII码冲突,所以中国制定了「GB2312码」,把中文编进去。但又由于全世界有很多语言,如果都各编各的话,在互相交流就容易产生乱码。
此时「Unicode」把所有语言都统一到一套编码。Unicode字符通常占 2 个字节或以上。Unicode至少占用 2 个字节,这又带来了问题:原本ASCII码中占 1 个字节的A
到Unicode要占 2 个字节,就浪费了一倍的存储空间。
为了节省空间,人们又发明了可变长「UTF-8」,它对一个不同范围的
Unicode 字符使用不同长度的编码,如英文字母被编码为 1 个字节,汉字通常为 3 个字节。可以看出,UTF-8包含了ASCII码。故此,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。
最后,用一幅图来总结这三种编码演变过程
常用三种字符编码演变过程
计算机系统通用的字符编码工作方式
现在计算机系统通用的字符编码工作方式:
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,一般就转换为UTF-8编码。
举个例子,当你记事本选择UTF-8格式保存时,记事本的内容按照UTF-8编码存放在硬盘中。而当你在编辑记事本内容(即内容被读取到内存中)
时,UTF-8的字符会被转换Unicode字符放在内存中。
Python2.x 字符串
因为 Python 诞生比 Unicode 早,所以一开始 Python 只支持 ASCII字符串,后面才加上Unicode字符串。故此,Python2.x的字符串的编码方式有两种 :
-
按ASCII编码,它是字符串默认的编码方式,对应的字符串类型是
str
-
按Unicode编码,对应的字符串类型是
unicode
举个例子
>>>type('hello')
<type 'str'>
>>> type(u'hello')
<type 'unicode'>
正因为两种编码方式共存并且我们经常会用到中文字符不包含在 ASCII
中,稍有转换不对就会抛出异常。以下两个异常最为多见:
第一种:「SyntaxError」: Non-ASCII character '\xe4' ...
问题描述
举个例子,在一个 .py 文件中
print u'中'
SyntaxError: Non-ASCII character '\xe4' in file E:\test.py on line 1,
but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
问题产生的原因
根据官方文档的解释是, Python2.x 的解释器默认按 ASCII 编码去读取源代码,而u'中'
并不是 ASCII 编码的字符串,所以解释器没法正确读取源代码。
总之,「如果解释器读取源代码按照默认编码方式 ASCII 的话,文件存在中文字符就会报错」
解决方法
官方文档给出的解决方案是在 .py 文件的第一行或第二行加上
# -*- coding: utf-8 -*-
用于申明 Python2.x 解释器按 UTF-8 编码读取文件。
PS :
这里需要注意一下,源代码文件按照 UTF-8 格式存储,仅代表在硬盘中字符按 UTF-8 编码存储。并不是指定 Python解释器读取源代码的编码方式。
但如果不按照 UTF-8 格式存储文件,可能下次打开文件,文件中的中文字符会变成乱码。
第二种:「UnicodeEncodeError」: 'ascii' codec can't encode characters ...
问题描述
举个例子,在一个 .py 文件中
# -*- coding: utf-8 -*-
str(u'中')
Traceback (most recent call last):
File "E:\test.py", line 3, in <module>
str(u'中文')
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
问题产生的原因
从异常的提示信息,知道了因为 ASCII 编解码器不能编码 Unicode 字符。
上面说到,Python2.x str()
的编码是 ASCII, ASCII 编解码器只通过范围0-127的字符,超过范围的字符就会报错。其中 u'中' 是 Unicode 且占2个字节,已经超过了127的范围。
总之,「如果存放将 Unicode 字符串强行转换为默认的字符串(str类型)的时候,就会报这种异常」
解决方案
# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
str(u'这')
通过 sys 模块修改解释器默认编码格式后,将默认编码格式也改成 UTF-8, 就变成 Unicode 转 UTF-8,此时转换就没问题了。
放在这个例子中,str是默认的字符串类型,编码方式是 ASCII,修改了解释器的默认编码格式,str的编码方式也跟着改变。
Python3.x 字符串
在 Python3 中,默认字符串编码改为了 Unicode,这样使得 Python 更好地支持多语言了。同样, Python3 依旧支持两种字符串,
- Unicode 字符串,是默认的编码格式,不需要在前面加u,对应的类型是 str
- Byte 字符串,编码格式是 ASCII,需要在字符串面前加b,例如
b'abc'
需要注意的是,
默认情况下,Python 源文件是 UTF-8 编码。在此编码下,全世界大多数语言的字符可以同时用在字符串、标识符和注释中 — 尽管 Python 标准库仅使用 ASCII 字符做为标识符,这只是任何可移植代码应该遵守的约定。如果要正确的显示所有的字符,你的编辑器必须能识别出文件是 UTF-8 编码,并且它使用的字体能支持文件中所有的字符。 Python3 源文件的第一行或者第二行依旧需要加上
# -*- coding: utf-8 -*-
通过此声明,源文件中所有的东西都会被当做用 encoding 指代的 UTF-8 编码对待。