Tips: 如果您已经充分理解问题是什么,请直接跳到 #问题出在哪里 一节。
字符串和编码
先从概念说起,字符串和它的编码是两个不同的概念:
字符串是一段文字本身,可以是中文可以是英文,以及各种语言
字符串的编码是计算机存储、处理字符串的方式;作为一种数据,它和其他数据一样,都是以一串0和1组成的,通常我们用字节数组来表示它。
字符串经过编码(encode) 就成为了一堆数据,反过来,数据经过解码(decode) 就变回我们认识的字符串。
从 Python 3 说起
这个编码问题(坑)可以说是 Python 2 被吐槽最多的黑点,没有之一。为了防止上来就掉进 Python 2 的坑里,我们先来看看 Python 3 里“改进”后是什么样子的。
1
2
3
4
5>>>s = "Hello, 世界"
>>>type(s)
>>> len(s)
9
哈,没有任何问题!(数长度的时候别漏了空格)
查阅文档,我们发现 str 有个函数叫 encode(),它看起来很眼熟,让我们来试试:
1
2
3>>>b = s.encode("utf-8")
>>>b
b'Hello, \xe4\xb8\x96\xe7\x95\x8c'
这个 b'' 的前缀表示返回值是一个 bytes 变量,也就是一堆数据了。
为什么这里面"Hello"还是原来的样子,但是“世界”变成一坨 \x?? 了?
这是因为 ASCII 实在太有名了,程序员们都看得懂:这个 H 其实表示的是一字节 0x48。而后面“世界”的编码不在 ASCII 的编码范围内,所以只能用 \x?? 表示了。
这样看起来也许更清晰一些:
1
2
3> >>> b.hex()
> '48656c6c6f2c20e4b896e7958c'
>
有 encode() 当然也有 decode()。我们对刚刚拿到的 bytes b 解码,果然会变成原来的字符串。
1
2>>>b.decode('utf-8')
'Hello, 世界'
OK,现在你已经明白了奥义所在,是时候去踩坑了。
Python 2 的世界
初见茅庐
先来一道开胃菜:
1
2
3
4
5>>>s = "Hello, 世界"
>>>type(s)
>>>len(s)
13
▲ 为什么这个长度是 13 ?明明是 9 个字符啊!
1
2>>>s
'Hello, \xe4\xb8\x96\xe7\x95\x8c'
▲ s 你怎么坏掉了?
1
2
3>>>b = s.encode('utf-8')
>>>b
'Hello, \xe4\xb8\x96\xe7\x95\x8c'
▲ 我可能用了假的 encode()
1
2>>>b.decode('utf-8')
u'Hello, \u4e16\u754c'
▲ 喵喵喵?
以上,Python 2 中字符串并不像我们想的那样工作。
问题出在哪里
其实说起来也简单,Python 是一门诞生于 1989 年的古老语言,比 Unicode 还要早两年,当时的程序员并不在乎编码问题,因为 ASCII 已经足够了。
如果你熟悉 C/C++ 会发现同样的问题:char* 被同时用于表示字符串和字节数组。Python 2 里也是同样,str 其实是个字节数组,却被挂上了字符串的名字。二十年后用着中文字符的我们被坑惨了。
后来 Python 2 为了支持 Unicode,增加了 unicode 类型,然而并没有卵用——程序员们不记得在每个字符串前面加上 u,这也不够优雅。
Python 3 设计之初就立志解决这个问题,不惜彻底修改了str的定义,把 str 这个名字让给了原来的 unicode!,而新增的 bytes 类型才是字节数组。如下表所示:
Python 2
Python 3
字符串(Unicode)
unicode
str
字节数组
str (bytes)
bytes
所以,在 Python 2 里,如果遇到非英语字符,一定要记得用 unicode。效果是这样的:
1
2
3
4
5
6
7
8
9
10
11
12>>>s = u'Hello, 世界'
>>>s
u'Hello, \u4e16\u754c'
>>>type(s)
>>>len(s)
9
>>>b = s.encode('utf-8')
>>>b
'Hello, \xe4\xb8\x96\xe7\x95\x8c'
>>>b.decode('utf-8')
u'Hello, \u4e16\u754c'
至于为什么 str 也有 encode(),主要是为了尽可能保持和 Python 3 的兼容性,以让部分程序能在 2、3 同时运行。于是事情变得更糟糕了。
原来如此
现在我们可以解释刚刚遇到的奇怪情况了:
“为什么这个长度是 13 ?明明是 9 个字符啊!”——因为 Python 自动帮你编码了,编码后是 13 个字节,常见的汉字在 UTF-8 编码下为 3 个字节
“s 你怎么坏掉了?” ——str 本来就是字节数组
“我可能用了假的 encode()”——你不应该对 str 变量做 encode,它本来就是编码后的
“喵喵喵?”——这是正常的,只是因为 Python 2 没有把 Unicode 字符显示成中文字符,用 print 就没问题了:
1
2>>>print s
Hello, 世界
解决方案
永远记住 str 其实是 bytes,字符串应该用 unicode,尤其是包含中文时
如果能说服你的老板和同事,尽快把 Python 2 升级到 3
最后,如果你需要写出兼容 Python 2\3 的程序,这篇文档可以给你一些帮助。