Python编码问题的一点理解
工作中遇到了许多Python2的程序, 其中str, unicode与byte给 我带来了许多的困扰. 在努力搞懂了这些编码问题后, 我也简单的谈谈自己的理解,
这篇文章中:
- 不会涉及任何utf8的来历或是编码方式(我既不会, 也并未深究).
- 介绍Python2的程序中存在的一些问题(处理中文或是特殊字符时).
- 程序如何应对从Python2到Python3的升级.
代码中常见的几个字符串操作
以下几个部分的片段均是Python2中可以运行的代码(Python3中或多或少有些问题)
1 | # 1 |
假如一个程序中出现了这么多字符串的相关花样, 我几乎可以确定, 这个Python2程序绝对
不是一个人写的, 有人注意到了unicode
的问题, 有人没有, 也有人为了兼容Python3
做出了努力. 但是, 我想说上面的程序是杂乱无章的, 对于接下来的维护,
或是想要把程序一点点进行升级时, 你就会发现程序的各处都要处理编码问题,
encode
与decode
可能出现在任何地方.
encode与decode
已经说过博客中不打算考虑太多编码的底层问题, 我就简要的说说Python字符处理的两个方式:
上面的处理形式是比较符合直观的理解, Python中, 通过encode与decode两个操作来进行
转换, 从字符串 => 字符数组
或是字符数组 => 字符串
.
在Python2中, 这样的转换是(这里的str
与bytes
是一种):
1 | +-----> unicode +-----+ |
在Python3中, 这样的转换是(这里的str
与bytes
不同, 并且将unicode
删除了):
1 | +-----> str +-----+ |
接回上一小节, 程序中如果到处出现encode与decode, 说明你在程序中混用了 这两种数据结构.
也许程序本来是一致的, 但是Python3的出现打破了这个一致, 根本原因是编码的不通用.
不一致带来的问题
对于Python这种弱类型语言来说, 在写代码时几乎无法获得某个变量的类型. 所有的对象的类型只有在执行到特定语句时才会确定. 也就是说, 你会陷入到一种玄学调试的境地里: 在某条编码出错的语句里, 添加encode或是decode 程序就又能运行了. 错误是调完了, 可是接手的人怎么办呢? 只能一边口吐芬芳, 一边默默承受.
为什么不能保持一致呢?
真心推荐大家阅读一下<<代码大全>>, 其中不止一次的强调了: 在程序内部保持统一. 这里一共有两种类型的数据:
- 可以进行
encode
操作的普通字符串 - 可以进行
decode
操作的字符数组
理想情况是程序中只保留其中一种数据, 这样根本不需要encode
或是decode
.
但是很遗憾, 即使我们自己的程序可以, 许多我们引用的库却不一定支持支持.
它们可能产生或是消费不同种类的数据.
这种时候, 我们所应该做的, 不是放任两种字符串混杂, 而是应该保证在程序内部 只有一种数据. 接受其他类库产生的数据后, 马上进行转换, 而使用其他类库时, 将我们的数据转换成它所需要的数据类型. 记住, 唯一的原则就是, 你写的程序中 只出现其中一种数据类型.
一致性的保持
当然, 写博客之前我也做了许多尝试, 下面我提出自己的解决方案, 大家也可以根据 现有的程序自己进行摸索. 不过唯一的原则就是保持一致.
在我的程序中, 所有的字符串都保存为
unicode
(这是针对Python2的说法), 也就是可以进行encode
操作的这一类数据.所有的其他类库产生的输出, 全部进行过滤(使用to_unicode函数)
所有向其他类库发出的调用, 按照类库的要求进行处理
1 | # 1 |
以下是我的to_unicode
与to_utf8
函数.
1 | if sys.version_info > (3, 0): |
你可以直接拷贝这部分代码, 把它当成基础库的一部分来使用, 确保程序中只有一种变量.
对于Python2 到 3的转换, 你几乎不需要修改一行代码, 同时, 程序中也不会
被encode
和decode
所淹没.
另外, 关于效率问题: 你都使用Python了, 还会在乎这么一点转换格式的效率吗, 这点效率的牺牲可以使得程序结构变得清晰的多, 我认为十分值得.
总结
这篇文章的本意并不是介绍编码问题的解决方案, 是希望大家阅读过后有自己对于程序 一致性的理解, 能够依照当前程序的具体代码来采取合适的方案, 一步步的优化迭代, 这才是我真正想表达的一些事情.