使用 python 的过程中, 或多或少都会接触一些字符编码的问题, 网上提到比较多的是 python2 中会有各种奇怪的编码问题, 很多人都会说换成 python3 之后就没有编码的烦恼了, 但事实上不是这样的只能说 python2 因为自身设计问题产生了更多编码的问题, 不能说换用 python3 之后就能彻底摆脱编码烦恼了既然因为历史原因, 产生了这么多种编码, 人们使用过程中也不能保持一致, 那么编码问题将会一直存在下去要想不被编码问题困扰, 最好的方法就是彻底了解其背后的机制
虽然编码问题网上已经有了大量文章, 但是没有一篇文章能把所有的问题涵盖, 所以我写了这个系列我看了网上非常多关于字符编码的博客和回答, 结合自己的使用, 尽可能全地总结平时可能遇到的编码问题有些普遍的问题很多博客都说的很清楚了我就贴上链接再简单叙述一下了
这个系列分为以下几个部分
编码问题的起源(文 1)
常见字符编码(文 1)
文件保存与打开中的编码问题(文 1)
python3 中的编码与解码原理(文 2)
python3 中的报错或乱码(文 2)
与文件网页交互时的报错或乱码(文 3)
python2 中的奇特编码问题(文 4)
本文包括前三个部分
编码问题的起源
常见字符编码
文件保存与打开中的编码问题
编码的起源
几乎每一篇讲编码的文章都会说一遍编码的起源, 详细的在这里就不多说了, 可以参考下面链接
廖雪峰 - 字符串和编码
python 之禅 - Python 编码错误的本质原因
总结起来就是
计算机只能计算数字, 为了表示字符, 使用二进制数来对应字符进行存储, 这种对应即所谓的编码
最初只有 ASCII 编码, 只包含英文字母和一些符号共 128 个
为了能用计算机表示中文, 中国人制定了 GB2312 编码同时各个国家都为自己的语言制定了一套编码
因为每个国家制定的编码无法兼容(比如在计算机中相同的二进制数在各个国家的编码中表示的字符不一样, 从而产生乱码), 所以最后统一出了一套 Unicode 标准
而 Unicode 比较占空间, 于是产生了可变长编码的 UTF-8 编码, 这时当前最通用的编码
常见编码
这里介绍一下我们平常会经常见到的编码, 了解了这些常见编码, 在遇到时就大概知道哪些编码是做什么的, 比如下面文本编辑器 sublime 中 reopen 可以使用的编码表
1.ASCII 编码
用 8 个比特(一个字节), 即 8 位的二进制数来表示一些符号其中包含了 26 个英文字母大小写字母 0-9 数字, 以及键盘上能看到的!@#$%^&*()_+{}|<>? 等符号因为 8 位之首一直是 0, 所以一共可以表示 128 个
这是最初产生的编码, 所以之后的所有编码几乎都兼容这种
2.EASCIIISO/8859-1Latin-1windows 1252
EASCII 是 ASCII 编码的扩展, 将 8 个比特数值全部填满, 可以表示 256 个数, 加入了一些公式符号希腊字母等不过 EASCII 并不表示某一种编码方式, 有很多种不同的扩充方式 ISO/8859-1 是其中一种, 又名 Latin-1windows 1252 是 windows 对应设计的一种
3.GBKGB2312
为了表示中文字符, 中国人最初指定了 GB2312, 可以表示绝大多数中文字符, 但是仍有少部分不能表示之后又扩充产生了完整表示中文字符的 GBK 编码 GBK 使用两个字节 (16 位二进制数) 来表示字符
在 windows 操作系统下, 我们经常看到 GBK 这种编码, 很多软件都默认用这个编码来保存和读取文件比如记事本默认使用 ANSI 保存文件, 常常就是使用 GBK 保存的文件(ANSI 后面会具体讲)
4.Unicode
这是国际组织制定的全球统一编码, 可以表示任意字符, 通常占用两个字节其实 Unicode 只是将每个字符对应一个数字使字符得到唯一标示, 它没有真正用于计算机存储, 真正根据 Unicode 设计用于存储的是下面的 UTF 系列编码
5.UTF-8
UTF-8 可以说是当前最推崇的一种编码方式, 可以表示任意字符, 同时它一种可变长的编码, 即表示不同字符的字节数量是不相同的比如英文就延续 ASCII 使用一个字节, 中文是 3 个字节, 更不常用的字符可能是 4-6 个字节
经常和 UTF-8 一起提到的还有
- UTF-16
- UTF-16 LE
- UTF-16 BE
带 BOM 的 UTF-8
无 BOM 的 UTF-8
这里只需要知道
UTF-8 和 UTF-16 都是 Unicode 的一种实现方式
UTF-16 是 2 个字节 (或少部分 4 字节) 的, 因此英文使用的是 2 字节我们通常说 UTF-8 可变长度在存储英文文件时比 Unicode 更省空间, 应该说的是比 UTF-16 省空间
UTF-16 的 LE 和 BE 的区别其实只是一些地方的字节顺序不同, 理解成两种有细微差别的编码方式即可
带 BOM 可以理解成保存时在最前面加一个标志, 让软件读取文件时知道这个文件是用什么进行的编码, 然后用相应方法解码
这部分没那么重要, 而且有点乱, 更详细的内容见下面这些链接
有没有 BOM 的更多差别可以看知乎的这个回答还有这个回答
一篇比较完整阐述的文章
6.ANSI
这是 windows 记事本中特有的, 记事本保存文本文件时默认使用的是 ANSI, 其看下面保存时的界面, 可以有 4 种选择
默认使用 ANSI, 但其实是使用 GBK 保存的经常默认使用 GBK 而不是 utf-8 保存打开文件应该是 windows 被诟病的一个点 (相比于 mac 和 linux) 有的人就会问为什么 windows 这么反人类, 不用兼容性好的 utf-8, 但是这有点冤枉 windows 了现在的 windows 其实使用的就是 unicode, 但是很多软件用的不是 unicode, 为了兼容那些软件, windows 设下这样的规定: 设置一个默认编码, 遇到一个字符串如果使用 unicode 就用 unicode, 不使用的就用这个默认语言来解释而这个默认语言在不同 windows 语言版本中是不一样的, 在简体中文版是 GBK, 在日语的 windows 系统中就是支持日语的编码 ANSI 就是这个默认编码, 因此在这里使用 ANSI 就相当于使用 GBK
更多内容参考这个回答和这个回答
关于记事本还要说明以下几点
其中 UTF-8 其实是带 BOM 的, 我们正常使用时最好使用不带 BOM 的 UTF-8 当我们在这里选择使用 UTF-8 保存时, 会自动在整个文档前面加一个标志性的东西即 BOM, 之后再读取时就可以知道这个是用 UTF-8 编码的, 从而使用 UTF-8 来解码带 BOM 的 UTF-8 是 windows 为了读取准确而设计的, 兼容性不是十分好, 所以不建议使用
如果使用 ANSI 保存, 没有 BOM 的标示, 在打开文件时, 记事本也无法判断文件是用什么编码的, 只能通过那些字节进行猜测, 所以有时会出现猜错的情况比如在记事本中输入联通两个字, 保存关闭, 之后再打开, 会发现乱码了是因为联通二字的编码比较像 UTF-8 的编码, 于是使用 UTF-8 来解释 GBK 编码的字符, 肯定会出现乱码如果文件联通后面还有很多字, 关闭打开就可以正常显示, 因为打开时可以参照的文字变多了, 就可以正确识别是用 GBK 编码的了记事本的更多 bug 见 这个回答
本节这些编码的更详细解释见这个回答
上面我们介绍了各种编码如何演变以及常见编码, 之后几个部分就来看一下哪些情况下会遇到乱码问题
文件保存与打开中的编码问题
本节分为如下部分
明确存储原理
txt 文件不同编码方式保存与打开试验
sublime 中关于编码的选项解释
如何知道一个文件的编码
R 文件乱码问题
python 脚本开头提示
明确存储原理
首先要明确, 我们人眼看到的英文字母文字等计算机是无法识别的, 也无法直接处理和储存这样的内容, 所以需要将这些字符编码成计算机可以识别的二进制数进行存储
比如在记事本中输入 A 中, 使用 UTF-8 保存文件, 记事本会将这些字符按照 UTF-8 的规则编码成
01000001 11100100 10111000 10101101
这样的二进制数, 将这些数存到硬盘中之后某一天我们想看文件中的内容, 用记事本打开, 它会将这一串二进制数又解码为我们能看懂的字符串因为在 UTF-8 下二进制编码和字符是一一对应的, 所以存储和打开时都使用 UTF-8 就不会出现乱码
而如果我们读取时使用 EASCII, 它的每个字符对应一个字节, 即上面存储的 4 个字节会被解码成 4 个字符, 显然不是我们最开始保存的 A 中了, 这种不一致就是乱码这段二进制数放在 EASCII 中可能得到的字符我们还认得, 而在有的编码方式中可能就对应着非常生僻的符号, 全篇解码出来都是这种看不懂的东西, 这就是我们脑中最直观的所谓乱码了
还可能你使用的解码方式中没有这段二进制数据对应的字符, 此时就会报错因为一种编码方式占用一些字节数, 这些字节可以表示的字符数量超过了当前需要表示的字符数量, 就会有一些二进制数不对应任何字符, 此时要将这些二进制数转成字符而报错
txt 文件不同编码方式保存与打开试验
首先推荐写代码时不要使用 windows 自带的记事本, 它对写代码的各种支持都不好可以选择自己称手的文本编辑器, 我用的是 sublime text3, 它一个优点是打开文件非常快这个文本编辑器支持以各种编码打开或保存文件, 所以这里拿这个编辑器对编码问题进行测试
为了更直观地体会乱码, 这里使用 windows 自带的记事本和文本编辑器 sublime text 进行对比
实验方法: 用记事本写下一段中文保存, 再用 sublime text 打开, 看结果这里输入
写下一段话很长的话再长一点
为了看使用不同编码方式打开结果, 用 sublime 打开后可以点击 file-reload with encoding(没有这个选项的看下一部分)来更换编码方式, 选择 GBK(中文简体)就正常显示, 选择 BIG5(中文繁体)发现文本乱码了, 选择 CP932(日文)报错了
如下图
sublime 中关于编码的选项解释
1. 选项说明 sublime 打开一个文件使用什么编码方式也是靠猜, 而保存一个文件默认方式是 UTF-8 它本身是不支持 GBK 解码的, 刚下载 sublime 时应该是没有
reload with encoding
和
set file encoding to
的, 只有
reopen with encoding
和 save with encoding
reopen with encoding
让我们可以在打开文件时指定用什么方式解码, 但是这里没有 GBK, 要想解码 GBK 就只好安装 ConvertToUTF8 这个插件(如何安装百度一下), 它提供了
reload with encoding
选项, 也是表示用什么编码解码文件, 这里提供了简体中文 (GBK) 繁体中文日文等编码 reload 和 reopen 的功能其实是一样的, 只是支持的编码方式不一样
安装好插件后, 大多数时候用记事本编辑的中文, 用 sublime 直接打开都不会乱码, 是因为它根据存储的二进制数可以猜出要用 GBK 来打开但是有的时候还是不行, 用记事本编辑的什么, 在 sublime 中打开就会乱码, 因为它识别不出来是 GBK, 使用了 UTF-8 来解码, 这时就要人为去选择 reload with encoding 中的 GBK, 就可以正常显示了
2. 在使用过程中发现一个奇怪的现象: 比如打开一个中文文件, 默认正常显示, 这是选择
reload with encoding
中的 BIG5 和 CP932 等都会出现乱码, 这是正常的, 而选择 UTF-8 没反应, 正常不也应该乱码吗? 此时如果用
reopen with encoding
中的所有选项都没反应, 然而事实上都应该乱码才对
摸索了很久发现一个规律, 就是如果打开时默认使用的是
reload with encoding
中的编码, 想切换成 UTF-8 或者
reopen with encoding
中的编码, 需要这样做
先点击 reload 中的 UTF-8, 再去点 reopen 中的 UTF-8 才是用 UTF-8 来解码
也就是说当前使用 reload 时想要切换到 reopen 需要先点 reload 中的 UTF-8
而如果当前使用 reopen 的编码时, 可以随意点 reopen 和 reload 中的编码都可以
reload 中的 UTF-8 无论何时都不起作用, 它只是 reload 到 reopen 的一座桥梁
知道这点之后我们就可以自由地对一些文本使用各种各样的编码方式进行解码了, 会发现即使是乱码很多种编码的结果都是一样的, 说明这些是相似的编码方式
3. 如何改变文件编码比如用记事本保存默认用的 GBK 编码, 可以在 sublime 中打开将文件编码方式转变成 UTF-8sublime 自带的选项是 save with encoding, 这里也不支持将文件转变成 GBK 进行保存, 于是 ConvertToUTF8 这个插件又增加了一个选项
set file encoding to
可以保存为 GBK, 这个设置完要再点一下保存
这里要说明一点, 比如当前显示中文字符, 用的是 GBK, 你想换成 UTF-8 编码, 它的内部原理是这样的
当前存储时使用的是 GBK 二进制编码, 每个字符对应两个字节, 计算机只能对这些字节进行操作
点击转换成 UTF-8 的过程是: 识别 GBK 中的字节(二进制数), 找到其对应的字符, 再找到字符对应的 UTF-8 二进制字节, 用新字节代替旧字节
如果这个文件实际上是用 GBK 编的码会正常显示中文, 而你用 BIG5 去打开文件 (解码) 得到一堆乱码, 此时再 save 转化为 UTF-8, 那么它去查询和替换的会是乱码字符的 UTF-8 二进制数, 所以相当于你保存下了这些乱码而把真正的东西丢掉了(虽然也可以往回推导找到原始文本)
如何知道一个文件的编码
对于一个未知的文件, 我们不能准确知道它的正确解码方式, 毕竟软件也都是靠猜, 因为很多种解码方式都可以得到一个结果, 有些结果在我们眼中叫做乱码, 但在计算机眼中都是一样的, 所以它无法辨别人眼到底能识别的是哪种结果
我们能知道的只有
当前软件使用了什么方式进行解码
这种方式解码结果是不是我们想要的
这个文件最有可能是哪几种编码方式, 去试
首先我们可以看出当前软件使用什么方式进行的解码
在记事本中, 点击另存为, 此时默认是什么编码就说明它是使用的这个方法解的码
在 sublime 中进行这样的配置, 在下方显示解码方式, 如图
其中左边一个, 右边一个左边对应的是插件里的编码, 右边对应的是 sublime 自带的看你打开文件时使用了什么编码, 如果是用 GBK, 那么右边显示的编码就完全没用不用看, 如果是用 reopen 中的编码, 则左边的不用看
比如一个中文文件, 我们使用 UTF-8 解码, 发现正常显示中文了, 说明我们的解码方式正确, 万事大吉如果乱码了, 说明文件不是用 UTF-8 编码的, 这时是无法得知确切编码的, 只能猜这个文件是一个中国同学发给我的, 他可能用的是 GBK, 于是去试 GBK 不行的话可能再想这个文件最初是一个日本人创建的辗转到我这里来, 于是再试日本的编码如果都不行就没辙了, 或者一个个试下来
R 文件乱码问题
在 weindows 下, R 语言中无论是 R 还是 rstudio, 如果有中文注释都是使用 GBK 进行保存但是最好将打开和保存文件设置为默认 UTF-8, 这在 rstudio 中是可以设置的 R 里面应该是不能设置的
如果要和别人一起完成工作, 他的电脑是 mac, 发给你的 R 文件都是 utf-8 编码的, 你打开的话中文 (比如注释) 就会乱码, 所以最好设置默认 UTF-8 但是我们在 rstudio 设置之后, 如果去打开以前的 R 文件, 其中的中文也会乱码; 那些没有设置过的 windows 小伙伴发来的. R 文件也会出现中文乱码, 但是这不是不换 UTF-8 的理由如果你要和 mac 系统的人合作, 同时要和日本人合作, 和韩国人合作, 他们默认保存的是 UTF-8 日本设计的编码韩国设计的编码, 而你使用的是中国设计的编码 GBK, 遇到编码不兼容问题, 最后商讨的结果一定是大家都换 UTF-8 来统一, 所以早换晚换都要换, 两个人编码不一样一定是非 UTF-8 的妥协早一点换会让你电脑中 GBK 的文件少一些, 之后修改编码方便一些
在 rstudio 中修改默认编码的方式: tool-global options-code-saving-change 选择 UTF-8
设置完我们用 R 和 rstudio 进行试验
在 R 中编辑脚本保存后用 rstudio 打开, 如下图
在 rstudio 中选择 reopen with encoding, 选择 GB2312 就可以正常显示中文了如果用 R 打开 rstudio 的 UTF-8 应该是没有方法进行转化的, 所以还是全用 rstudio 就好
在 rstudio 中也可以选择 save with encoding 改变文件的编码方式
python 脚本开头提示
我们经常会看到一些 python 脚本文件 (.py 文件) 最开始两行总是这样的
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
其中第一行是在 Linux/OS X 中声明这是一个可执行的 python 程序, 在 windows 下无用, 可以忽略
第二行是告诉 Python 解释器, 按照 UTF-8 编码读取脚本代码
但是在 python3 中其实第二条也没有必要了, 参考这个回答
专栏信息
专栏主页: python 编程
专栏目录: 目录
版本说明: 软件及包版本说明
来源: https://juejin.im/entry/5aa7365a518825557e780c06