写点什么

也谈 Python 编码格式

用户头像
ITCamel
关注
发布于: 2021 年 01 月 15 日

python 在升级到 Python3 之后,因为 Utf-8 作为没有歧义的统一标准编码,相信很少人再会碰到编码格式的问题,但现实总会不停地打脸理想,告诉我们 Too Young Too Simple。先不扯闲篇,直入主题,介绍一下这篇博文主要的知识,后面再结合具体的软件工作情境来详细说明为什么会碰到此类问题。


  1. Python2 中纠结的编码格式。

  2. Python3 中的大一统。

  3. 实战分析

  4. 小结


Python2 中的编码格式


首先我们需要理解,在计算机中,所有的内容,归根结底就是一个个的字节甚至是 0 与 1,没有中文日文也没有 abcd。但为了在磁盘中存储这些文字,我们发明了编码。比如一个字符'a'被编码成了十进制的 97,这是 ASCII 编码,但是中文表达不了啊,于是又有了一种编码格式 GBK,但是日文表达不了啊,算了,大一统吧,于是就有了 Utf-8,可变字节,可以表示基本当前所有的字符了。而解码呢,就是将编码的二进制再还原为抽象的字符'a',或者一个文字'中',这个抽象的字符,就可以理解为 Unicode 字符。

有了上面的基础,我们就来看看有哪些地方需要用到编码吧。


#!/usr/bin/env python2# -*- coding:GB2312 
def main(): a = '中文' with open('E:/a.txt', 'w') as f: f.write(a)
if __name__ == '__main__': main()
复制代码

>此处的声明有两个用处,一是解释器将以怎样的编码格式来读取源码文件,二是出现在内存中的字符变量是被怎样编码的。基于前者,我们应该保证我们的源码文件在保存时,也应该选择 GB2312 的格式来保存,基于后者,我们可以预期,输出的 a.txt 文件的格式也将是 GB2312。

那不好意思,我想输出文件的格式为 utf-8,这可咋整?先把这个变量解码为 Unicode,然后再重新编码成 utf-8 的字符输出就行了。


#!/usr/bin/env python2# -*- coding:gb2312 -*-def main():    a = '中文'    b = a.decode('gb2312') # Unicode类型,也就是b = u'中文'    c = b.encode('utf-8')    with open('E:/c.txt', 'w') as f:        f.write(c)

if __name__ == '__main__': main()
复制代码


所以,对于'中文'这两个字符来说,在上面的程序中,就有了 3 个变量,a 和 c 都是 str,而 b 是 Unicode。而 Unicode 因为是个抽象出来的东西,所以无论是 print 还是想 f.write 都是不收的,因为输出都需要确切的编码成二进制的数据,就向水通过不同的管道流向不同的容器。这里也可以顺便解释一下为什么有的时候,print 在 IDE 里面执行得好好的,到了命令行控制台就成了乱码呢?因为不同的控制吧,其默认的解码方式不一样,你像中文的 Windows 操作系统,就是 GB2312 的,而 Linux 或者 IDE 中的输出控制台则默认为 Utf-8 的。

想到上面程序中的 abc,会不会脑壳疼? 那接下来看 python3 怎样破而后立。


Python3 中的大一统


从使用者的角度来看,Python3 最大的升级就是将 Python2 中混乱的编码格式进行了统一。既然上面程序中的 a 和 c,编码格式看起来是让人混乱的,那我们索性就统统不要,将其统一到 Unicode 上来。所以,在 Python3 中, 基本的字符表示使用类型 str,可以将其理解为 Unicode,而我要编编码后的对象怎么表示呢,来来来,bytes 类型也已经给你准备好了。就这样?对,就这样。


实战分析


注意:以下的场景,除非特别说明,都以 Python3 作为编码语言。


  • 写文件


>在 python3 中,因为 str 类型即为 Unicode,不与具体编码挂钩,所以,在输出时只需要使用``file`对象的`write`方法的`encoding``参数来指定编码格式即可,默认为 utf-8。


  • 读文件

写文件,我们可以指定编码格式,那么读文件我们应该使用什么编码格式来读取文件呢?因为历史原因,有些文件格式很古老,在创建之初,并没有想到后面还会有这么多的编码格式,这么多的程序需要来对其进行解析,所以这个坑就挖下了。理想情况,大家都用 utf-8,次之,大家在文件开始都声明一下自己的编码格式,但很遗憾,这两点都因为历史已经形成而无法做到,那么剩下的其实就是。你没有看错,其实就是。从使用最广泛的编码开始试,如果通过了,就表示 Ok,如果有错,就换个再试试。那如果一个文件很变态,前面一种编码,后面一种编码呢?或者前面用 ascii 编码解析的都 OK,后面多了中文又解析不了呢,而操蛋的是这个中文在 1GB 之后的位置出现了!惊喜不惊喜?讲这么多是想告诉大家,这个问题没有最优解。就是拿出文件中的一段来进行解析尝试。


import chardet
def get_file_encoding(file_path): """ 获取文件的编码格式 :param file_path: :return: """ with open(file_path, "rb") as f: data = f.read(1024*1024) encoding = chardet.detect(data).get('encoding') if encoding.lower() == 'gb2312': encoding = 'gb18030' if encoding.startswith('ISO-8859-'): encoding = 'gbk' # 用来解决出现一些奇奇怪怪地编码 if encoding.lower() not in ('gbk', 'gb18030', 'utf-8', 'utf-16', 'ascii', 'utf-8-sig'): encoding = 'gb18030' return encoding
复制代码


为什么会有这么奇怪的方法?chardet 搞不定么?真搞不定。如果代码可以优化,请大家务必告诉我。


小结


绝大多数的程序,都是接受一段输入,经过运算后,进行一段输出。我们无法控制这种输出的来源,可能是控制台输入,可能是网络端口,可能是 Unix 中的一段通信管道,或者是各种文件。那为了我们处理中的一致性,我觉得我们应该在 python3 中统一使用 str,而在 python2 中 unicode。而在输出时,统一为 utf-8 的编码,不要再为后来者挖坑。在此,请允许我摘录自《编写高质量 Python 代码的 59 个有效方法》中的一段辅助函数,做为结尾。


# python3def to_str(bytes_or_str):    if isinstance(bytes_or_str, bytes):        value = bytes_or_str.decode('utf-8')    else:        value = bytes_or_str    return value
# python2def to_unicode(unicode_or_str): if isinstance(unicode_or_str, unicode): value = unicode_or_str.decode('utf-8') else: value = unicode_or_str return value
复制代码


发布于: 2021 年 01 月 15 日阅读数: 28
用户头像

ITCamel

关注

努力生活,快乐编程 2020.08.06 加入

一个在3D软件行业的程序员和创业者

评论

发布
暂无评论
也谈Python编码格式