这篇文章主要介绍了再谈 Python 中的字符串与字符编码(推荐),具有一定的参考价值,有需要的可以了解一下。
Python 是一种面向对象、解释型计算机程序设计语言,由 Guido van Rossum 于 1989 年底发明,第一个公开发行版发行于 1991 年。Python 语法简洁而清晰,具有丰富和强大的类库。它常被昵称为胶水语言,它能够把用其他语言制作的各种模块(尤其是 C/C++)很轻松地联结在一起。
本节内容:
1. 前言
2. 相关概念
3.Python 中的默认编码
4.Python2 与 Python3 中对字符串的支持
5. 字符编码转换
一、前言
Python 中的字符编码是个老生常谈的话题,同行们都写过很多这方面的文章。有的人云亦云,也有的写得很深入。近日看到某知名培训机构的教学视频中再次谈及此问题,讲解的还是不尽人意,所以才想写这篇文字。一方面,梳理一下相关知识,另一方面,希望给其他人些许帮助。
Python2 的 默认编码 是 ASCII,不能识别中文字符,需要显式指定字符编码;Python3 的 默认编码 为 Unicode,可以识别中文字符。
相信大家在很多文章中都看到过类似上面这样 "对 Python 中中文处理" 的解释,也相信大家在最初看到这样的解释的时候确实觉得明白了。可是时间久了之后,再重复遇到相关问题就会觉得貌似理解的又不是那么清楚了。如果我们了解上面说的默认编码的作用是什么,我们就会更清晰的明白那句话的含义。
二、相关概念
1. 字符与字节
一个字符不等价于一个字节,字符是人类能够识别的符号,而这些符号要保存到计算的存储中就需要用计算机能够识别的字节来表示。一个字符往往有多种表示方法,不同的表示方法会使用不同的字节数。这里所说的不同的表示方法就是指字符编码,比如字母 A-Z 都可以用 ASCII 码表示(占用一个字节),也可以用 UNICODE 表示(占两个字节),还可以用 UTF-8 表示(占用一个字节)。字符编码的作用就是将人类可识别的字符转换为机器可识别的字节码,以及反向过程。
UNICDOE 才是真正的字符串,而用 ASCII、UTF-8、GBK 等字符编码表示的是字节串。关于这点,我们可以在 Python 的官方文档中经常可以看到这样的描述 "Unicode string" , "translating a Unicode string into a sequence of bytes"
我们写代码是写在文件中的,而字符是以字节形式保存在文件中的,因此当我们在文件中定义个字符串时被当做字节串也是可以理解的。但是,我们需要的是字符串,而不是字节串。一个优秀的编程语言,应该严格区分两者的关系并提供巧妙的完美的支持。JAVA 语言就很好,以至于了解 Python 和 PHP 之前我从来没有考虑过这些不应该由程序员来处理的问题。遗憾的是,很多编程语言试图混淆 "字符串" 和 "字节串",他们把字节串当做字符串来使用,PHP 和 Python2 都属于这种编程语言。最能说明这个问题的操作就是取一个包含中文字符的字符串的长度:
注意:Windows 的 cmd 终端字符编码默认为 GBK,因此在 cmd 输入的中文字符需要用两个字节表示
- >>> # Python2
- >>> a = 'Hello,中国' # 字节串,长度为字节个数 = len('Hello,')+len('中国') = 6+2*2 = 10
- >>> b = u'Hello,中国' # 字符串,长度为字符个数 = len('Hello,')+len('中国') = 6+2 = 8
- >>> c = unicode(a, 'gbk') # 其实b的定义方式是c定义方式的简写,都是将一个GBK编码的字节串解码(decode)为一个Uniocde字符串
- >>>
- >>> print(type(a), len(a))
- (<type 'str'>, 10)
- >>> print(type(b), len(b))
- (<type 'unicode'>, 8)
- >>> print(type(c), len(c))
- (<type 'unicode'>, 8)
- >>>
Python3 中对字符串的支持做了很大的改动,具体内容会在下面介绍。
2. 编码与解码
先做下科普:UNICODE 字符编码,也是一张字符与数字的映射,但是这里的数字被称为代码点 (code point), 实际上就是十六进制的数字。
Python 官方文档中对 Unicode 字符串、字节串与编码之间的关系有这样一段描述:
Unicode 字符串是一个代码点(code point)序列,代码点取值范围为 0 到 0x10FFFF(对应的十进制为 1114111)。这个代码点序列在存储(包括内存和物理磁盘)中需要被表示为一组字节 (0 到 255 之间的值),而将 Unicode 字符串转换为字节序列的规则称为编码。
这里说的编码不是指字符编码,而是指编码的过程以及这个过程中所使用到的 Unicode 字符的代码点与字节的映射规则。这个映射不必是简单的一对一映射,因此编码过程也不必处理每个可能的 Unicode 字符,例如:
将 Unicode 字符串转换为 ASCII 编码的规则很简单 -- 对于每个代码点:
将 Unicode 字符串转换为 UTF-8 编码使用以下规则:
简单总结:
可见,无论是编码还是解码,都需要一个重要因素,就是特定的字符编码。因为一个字符用不同的字符编码进行编码后的字节值以及字节个数大部分情况下是不同的,反之亦然。
三、Python 中的默认编码
1. Python 源代码文件的执行过程
我们都知道,磁盘上的文件都是以二进制格式存放的,其中文本文件都是以某种特定编码的字节形式存放的。对于程序源代码文件的字符编码是由编辑器指定的,比如我们使用 Pycharm 来编写 Python 程序时会指定工程编码和文件编码为 UTF-8,那么 Python 代码被保存到磁盘时就会被转换为 UTF-8 编码对应的字节(encode 过程)后写入磁盘。当执行 Python 代码文件中的代码时,Python 解释器在读取 Python 代码文件中的字节串之后,需要将其转换为 UNICODE 字符串(decode 过程)之后才执行后续操作。
上面已经解释过,这个转换过程(decode,解码)需要我们指定文件中保存的字节使用的字符编码是什么,才能知道这些字节在 UNICODE 这张万国码和统一码中找到其对应的代码点是什么。这里指定字符编码的方式大家都很熟悉,如下所示:
- # -*- coding:utf-8 -*-
2. 默认编码
那么,如果我们没有在代码文件开始的部分指定字符编码,Python 解释器就会使用哪种字符编码把从代码文件中读取到的字节转换为 UNICODE 代码点呢?就像我们配置某些软件时,有很多默认选项一样,需要在 Python 解释器内部设置默认的字符编码来解决这个问题,这就是文章开头所说的 "默认编码"。因此大家所说的 Python 中文字符问题就可以总结为一句话:当无法通过默认的字符编码对字节进行转换时,就会出现解码错误 (UnicodeEncodeError)。
Python2 和 Python3 的解释器使用的默认编码是不一样的,我们可以通过 sys.getdefaultencoding() 来获取默认编码:
- >>> # Python2
- >>> import sys
- >>> sys.getdefaultencoding()
- 'ascii'
- >>> # Python3
- >>> import sys
- >>> sys.getdefaultencoding()
- 'utf-8'
因此,对于 Python2 来讲,Python 解释器在读取到中文字符的字节码尝试解码操作时,会先查看当前代码文件头部是否有指明当前代码文件中保存的字节码对应的字符编码是什么。如果没有指定则使用默认字符编码 "ASCII" 进行解码导致解码失败,导致如下错误:
SyntaxError: Non-ASCII character '\xc4' in file xxx.py on line 11, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
对于 Python3 来讲,执行过程是一样的,只是 Python3 的解释器以 "UTF-8" 作为默认编码,但是这并不表示可以完全兼容中文问题。比如我们在 Windows 上进行开发时,Python 工程及代码文件都使用的是默认的 GBK 编码,也就是说 Python 代码文件是被转换成 GBK 格式的字节码保存到磁盘中的。Python3 的解释器执行该代码文件时,试图用 UTF-8 进行解码操作时,同样会解码失败,导致如下错误:
SyntaxError: Non-UTF-8 code starting with '\xc4' in file xxx.py on line 11, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
3. 最佳实践
四、Python2 与 Python3 中对字符串的支持
其实 Python3 中对字符串支持的改进,不仅仅是更改了默认编码,而是重新进行了字符串的实现,而且它已经实现了对 UNICODE 的内置支持,从这方面来讲 Python 已经和 JAVA 一样优秀。下面我们来看下 Python2 与 Python3 中对字符串的支持有什么区别:
Python2
Python2 中对字符串的支持由以下三个类提供
- class basestring(object)
- class str(basestring)
- class unicode(basestring)
执行 help(str) 和 help(bytes) 会发现结果都是 str 类的定义,这也说明 Python2 中 str 就是字节串,而后来的 unicode 对象对应才是真正的字符串。
- #!/usr/bin/env python
- # -*- coding:utf-8 -*-
- a = '你好'
- b = u'你好'
- print(type(a), len(a))
- print(type(b), len(b))
输出结果:
(
(<type'unicode'>, 2)
Python3
Python3 中对字符串的支持进行了实现类层次的上简化,去掉了 unicode 类,添加了一个 bytes 类。从表面上来看,可以认为 Python3 中的 str 和 unicode 合二为一了。
- class bytes(object)
- class str(object)
实际上,Python3 中已经意识到之前的错误,开始明确的区分字符串与字节。因此 Python3 中的 str 已经是真正的字符串,而字节是用单独的 bytes 类来表示。也就是说,Python3 默认定义的就是字符串,实现了对 UNICODE 的内置支持,减轻了程序员对字符串处理的负担。
- #!/usr/bin/env python
- # -*- coding:utf-8 -*-
- a = '你好'
- b = u'你好'
- c = '你好'.encode('gbk')
- print(type(a), len(a))
- print(type(b), len(b))
- print(type(c), len(c))
输出结果:
<class'bytes'> 4
五、字符编码转换
上面提到,UNICODE 字符串可以与任意字符编码的字节进行相互转换,如图:
那么大家很容易想到一个问题,就是不同的字符编码的字节可以通过 Unicode 相互转换吗?答案是肯定的。
Python2 中的字符串进行字符编码转换过程是:
字节串 -->decode('原来的字符编码')-->Unicode 字符串 -->encode('新的字符编码')--> 字节串
- #!/usr/bin/env python
- # -*- coding:utf-8 -*-
- utf_8_a = '我爱中国'
- gbk_a = utf_8_a.decode('utf-8').encode('gbk')
- print(gbk_a.decode('gbk'))
输出结果:
我爱中国
Python3 中定义的字符串默认就是 unicode,因此不需要先解码,可以直接编码成新的字符编码:
字符串 -->encode('新的字符编码')--> 字节串
- #!/usr/bin/env python
- # -*- coding:utf-8 -*-
- utf_8_a = '我爱中国'
- gbk_a = utf_8_a.encode('gbk')
- print(gbk_a.decode('gbk'))
输出结果:
我爱中国
最后需要说明的是,Unicode 不是有道词典,也不是 google 翻译器,它并不能把一个中文翻译成一个英文。正确的字符编码的转换过程只是把同一个字符的字节表现形式改变了,而字符本身的符号是不应该发生变化的,因此并不是所有的字符编码之间的转换都是有意义的。怎么理解这句话呢?比如 GBK 编码的 "中国" 转成 UTF-8 字符编码后,仅仅是由 4 个字节变成了 6 个字节来表示,但其字符表现形式还应该是 "中国",而不应该变成 "你好" 或者 "China"。
前面花了很大的篇幅介绍概念和理论,后面注重实践,希望对他人有所帮助。
来源: http://www.phperz.com/article/17/0320/315075.html