在之前的文章中, 我们介绍过编码格式的发展史:[文章传送门 - todo]. 今天我们通过几个例子, 来彻底搞清楚 python3 中的编码格式原理, 这样你之后写 python 脚本时碰到编码问题, 才能有章可循.
我们先搞清楚几个概念:
系统默认编码: 指 python 解释器默认的编码格式, 在 python 文件头部没有声明其他编码格式时, python3 默认的编码格式是 utf-8.
本地默认编码: 操作系统默认的编码, 常见的 Windows 的默认编码是 gbk,Linux 的默认编码是 UTF-8.
python 文件头部声明编码格式: 修改的是文件的默认编码格式, 只是会影响 python 解释器读取 python 文件时的编码格式, 并不会改变系统默认编码和本地默认编码.
通过 python 自带的库, 可以查看系统默认编码和本地默认编码
- Python 3.7.4 (tags/v3.7.4:e09359112e, Jul 8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
- Type "help", "copyright", "credits" or "license" for more information.
- >>> import sys
- >>> sys.getdefaultencoding()
- 'utf-8'
- >>> import locale
- >>> locale.getdefaultlocale()
- ('zh_CN', 'cp936')
- >>>
注意, 因为我在 Windows 系统的电脑上 进行测试, 所以系统默认编码返回 "cp936", 这是代码页(是字符编码集的别名), 而 936 对应的就是 gbk. 如果你在 Linux 或者 Mac 上执行上面的代码, 应该会返回 utf-8 编码.
其实总结来看, 容易出现乱码的场景, 基本都与读写程序有关, 比如: 读取 / 写入某个文件, 或者从网络流中读取数据等, 因为这个过程中涉及到了编码 和解码的过程, 只要编码和解码的编码格式对应不上, 就容易出现乱码. 下面我们举两个具体的例子, 来验证下 python 的编码原理, 帮助你理解这个过程. 注意: 下面的例子都是在 pycharm 中写的.
01 默认的编码格式
我们新建一个 encode_demo.py 的文件, 其文件默认的编码格式是 UTF-8(可以从 pycharm 右下角看到编码格式), 代码如下:
- """
- @author: asus
- @time: 2019/11/21
- @function: 验证编码格式
- """
- import sys, locale
- def write_str_default_encode():
- s = "我是一个 str"
- print(s)
- print(type(s))
- print(sys.getdefaultencoding())
- print(locale.getdefaultlocale())
- with open("utf_file", "w", encoding="utf-8") as f:
- f.write(s)
- with open("gbk_file", "w", encoding="gbk") as f:
- f.write(s)
- with open("jis_file", "w", encoding="shift-jis") as f:
- f.write(s)
- if __name__ == '__main__':
- write_str_default_encode()
我们先来猜测下结果, 因为我们没有声明编码格式, 所以 python 解释器默认用 UTF-8 去解码文件, 因为文件默认编码格式就是 UTF-8, 所以字符串 s 可以正常打印. 同时以 UTF-8 编码格式写文件不会出现乱码, 而以 gbk 和 shift-jis(日文编码)写文件会出现乱码(这里说明一点, 我是用 pycharm 直接打开生成的文件查看的, 编辑器默认编码是 UTF-8, 如果在 Windows 上用记事本打开则其默认编码跟随系统是 GBK,gbk_file 和 utf_file 均不会出现乱码, 只有 jis_file 是乱码), 我们运行看下结果:
# 运行结果
我是一个 str
- <class 'str'>
- utf-8 ('zh_CN', 'cp936') # 写文件 utf_file,gbk_file,jis_file 文件内容分别是:
我是一个 str
һstr
str
和我们猜测的结果一致, 下面我们做个改变, 在文件头部声明个编码格式, 再来看看效果.
02 python 头文件声明编码格式
因为上面文件 encode_demo.py 的格式是 UTF-8, 那么我们就将其变为 gbk 编码. 同样的我们先来推测下结果, 在 pycharm 中, 在 python 文件头部声明编码为 gbk 后(头部加上 # coding=gbk ), 文件的编码格式变成 gbk, 同时 python 解释器会用 gbk 去解码 encode_demo.py 文件, 所以运行结果应该和用 UTF-8 编码时一样. 运行结果如下:
# 运行结果
我是一个 str
- <class 'str'>
- utf-8 ('zh_CN', 'cp936') # 写文件 utf_file,gbk_file,jis_file 文件内容分别是:
我是一个 str
һstr
str
结果确实是一样的, 证明我们推论是正确的. 接下来我们再做个尝试, 假如我们将 (# coding=gbk) 去掉(需要注意, 在 pycharm 中将 # coding=gbk 去掉, 并不会改变文件的编码格式, 也就是说 encode_demo.py 还是 gbk 编码), 我们再运行一次看结果:
- File "D:/codespace/python/pythonObject/pythonSample/basic/encodeDemo/encode_demo.py", line 4
- SyntaxError: Non-UTF-8 code starting with '\xd1' in file D:/codespace/python/pythonObject/pythonSample/basic/encodeDemo/encode_demo.py on line 5, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
运行直接报错了, 我们加个断点, 看看具体的异常信息:
看错误提示是 UnicodeDecodeError,python 解释器在对 encode_demo.py 文件解码时, 使用默认的 UTF-8 编码, 但是文件本身是 gbk 编码, 所以当碰到有中文没办法识别时, 就抛出 DecodeError.
03 敲黑板, 划重点
python3 中的 str 和 bytes
python3 的重要特性之一就是对字符串和二进制流做了严格的区分, 我们声明的字符串都是 str 类型, 不过 Str 和 bytes 是可以相互转换的:
- def str_transfor_bytes():
- s = '我是一个测试 Str'
- print(type(s))
- # str 转 bytes
- b = s.encode()
- print(b)
- print(type(b))
- # bytes 转 str
- c = b.decode('utf-8')
- print(c)
- print(type(c))
- if __name__ == '__main__':
- str_transfor_bytes()
需要注意一点: 在调用 encode()和 decode()方法时, 如果不传参数, 则会使用 python 解释器默认的编码格式 UTF-8(如果不在 python 头文件声明编码格式). 但是如果传参的话, encode 和 decode 使用的编码格式要能对应上.
python3 默认编码是 UTF-8? 还是 Unicode?
经常在很多文章里看到, python3 的默认编码格式是 Unicode, 但是我在本文中却一直在说 python3 的默认编码格式是 UTF-8, 那么哪种说法是正确的呢? 其实两种说法都对, 主要得搞清楚 Unicode 和 UTF-8 的区别(之前文章有提到):
Unicode 是一个字符集, 说白了就是把各种编码的映射关系全都整合起来, 不过它是不可变长的, 全部都以两个字节或四个字节来表示, 占用的内存空间比较大.
UTF-8 是 Unicode 的一种实现方式, 主要对 Unicode 码的数据进行转换, 方便存储和网络传输 . 它是可变长编码, 比如对于英文字母, 它使用一个字节就可以表示.
在 python3 内存中使用的字符串全都是 Unicode 码, 当 python 解释器解析 python 文件时, 默认使用 UTF-8 编码.
open()方法默认使用本地编码
在上面的例子中, 我们往磁盘写入文件时, 都指定了编码格式. 如果不指定编码格式, 那么默认将使用操作系统本地默认的编码格式, 比如: Linux 默认是 UTF-8,Windows 默认是 GBK. 其实这也好理解, 因为和磁盘交互, 肯定要考虑操作系统的编码格式. 这有区别于 encode()和 decode()使用的是 python 解释器的默认编码格式, 千万别搞混淆了.
总结
不知道你看完上面的例子后, 是否已经彻底理解了 python3 的编码原理. 不过所有的编码问题, 都逃不过 "编码" 和 "解码" 两个过程, 当你碰到编码问题时, 先确定源文件使用的编码, 再确定目标文件需要的编码格式, 只要能匹配, 一般就可以解决编码的问题.
来源: https://www.cnblogs.com/zhouliweiblog/p/11909554.html