编程笔记 | 字符编码、Python字符串以及常见异常

96
数据搬瓦工
2018.04.12 10:22* 字数 1662

在讲 Python 字符串之前先了解一下常用的字符编码

常用三种字符编码发展

常用的三种字符编码与特点:

  1. ASCII码 :最早
  2. Unicode : 把所有语言都统一到一套编码
  3. 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的字符串的编码方式有两种 :

  1. 按ASCII编码,它是字符串默认的编码方式,对应的字符串类型是str
  2. 按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 依旧支持两种字符串,

  1. Unicode 字符串,是默认的编码格式,不需要在前面加u,对应的类型是 str
  2. Byte 字符串,编码格式是 ASCII,需要在字符串面前加b,例如b'abc'

需要注意的是,
默认情况下,Python 源文件是 UTF-8 编码。在此编码下,全世界大多数语言的字符可以同时用在字符串、标识符和注释中 — 尽管 Python 标准库仅使用 ASCII 字符做为标识符,这只是任何可移植代码应该遵守的约定。如果要正确的显示所有的字符,你的编辑器必须能识别出文件是 UTF-8 编码,并且它使用的字体能支持文件中所有的字符。 Python3 源文件的第一行或者第二行依旧需要加上

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

通过此声明,源文件中所有的东西都会被当做用 encoding 指代的 UTF-8 编码对待。

Python笔记
Web note ad 1