浅谈 Python 2 中的编码问题

简介: Python 2.x 里的编码实在是一件令人烦躁的事情。不断有初学者被此问题搞得晕头转向。我自己也在很长一段时间内深受其害,直到现在也仍会在开发中偶尔被坑。在本教室的提问和讨论中,编码问题也占据了相当大的比重。


Python 2.x 里的编码实在是一件令人烦躁的事情。不断有初学者被此问题搞得晕头转向。我自己也在很长一段时间内深受其害,直到现在也仍会在开发中偶尔被坑。在本教室的提问和讨论中,编码问题也占据了相当大的比重。


然而这个问题并不能一两句话轻易解答。今天在这里稍微分析一下,希望能帮各位理清这里面的问题。


要弄清编码问题,首先明确几个概念:


str、unicode、encode、decode



str


就是我们通常说的字符串,在 python 中是由引号包围的一串字符。但是 Python 中的默认字符并不包括中文及其他复杂字符(其他非英语语言、特殊符号等)。虽然你可以定义"你好"这样的字符串,但在 Python Shell 中输入一下你就会发现:


>>> '你好'
'\xe4\xbd\xa0\xe5\xa5\xbd'>>>


在程序中,这两个字符是被其他的一些按照某种格式的普通字符所表示。进一步地,输入:


>>> len('你好')
6


字符串的长度也并不是想象中的2。


这就是我们一直说的编码。即通过某种规定的形式,用一些字符表示另一些字符。目的就是为了用少量的简单字符表示更多更复杂的字符。


上述的结果,是因为我的 Python Shell 里默认使用 UTF-8 对字符进行了编码。如果你在 Windows 下进行尝试,会是不一样的结果,因为 Windows 默认使用的是一种叫做 cp936 的编码。


当你需要通过 Python 得到某些输入或者输出,比如读取网页,输出到控制台,读写文件等等,需要处理的都是 str 类型。所以必然逃不过编码的问题。


unicode


为了处理不同编码的字符,于是有了 unicode。unicode 本身是一种编码,因为足够的长度,它可以包容各种文字和符号。同时它也是 Python 中的一种类型。在表示形式上,是字符串的引号前加上一个 u。比如


>>> u'你好'
u'\u4f60\u597d'
>>> type(u'你好')
<type 'unicode'>
>>> type('你好')
<type 'str'>


可以看出,unicode 和 str 是两种不同的类型。


虽然 unicode 很强大,但在 Python 2 中,它不能被直接输出,而必须通过某种编码转成 str。


encode & decode


encode 是 unicode 的一种方法,作用是按照某种形式对其进行编码,转为 str。如:


>>> u'你好'.encode('utf-8')
'\xe4\xbd\xa0\xe5\xa5\xbd'


decode 是 str 的一种方法,作用是按照某种形式对其进行解码,转为 unicode。如:


>>> '你好'.decode('utf-8')
u'\u4f60\u597d'


对 unicode 进行 encode 没太大问题,但对 str decode 时,因为 str 本身是有某种编码的,这时候如果指定的编码不符,就会产生讨厌的乱码:


>>> '你好'.decode('cp936')
u'\u6d63\u72b2\u30bd'
>>> print '你好'.decode('cp936')
浣犲ソ


在默认的 utf-8 编码环境下强行使用 cp936 编码,就会出现乱码。



小结一下就是:


输入 -> str -> decode -> unicode -> encode -> str -> 输出


那么通常问题出在哪里呢?


一般情况下,我们在程序里做的只是


输入 -> str -> 逻辑处理 -> str -> 输出


但在输入输出过程和中间的逻辑处理时,很可能 Python 帮我们默认做了一些 encode 和 decode 的工作。比如说,print 会按照环境的默认形式进行编码,当在需要 unicode 类型的操作而程序拿到的是一个 str 类型是,会使用 ascii 进行解码。前者将可能导致乱码显示,而后者就直接报错。


举两个例子:


1

程序从网上抓取一段网页,中间的文字是 gbk 编码,如 '\xbb\xb6\xd3\xad'(欢迎)。但抓取下来,从默认 utf-8 控制台输出时,就变成了 ��ӭ。同理存在于,Windows 下抓取了 utf-8 编码的网页。正确的处理方法是手动做一次解码:


>>> s = '\xbb\xb6\xd3\xad'
>>> print s
��ӭ
>>> s.decode('gbk')
u'\u6b22\u8fce'
>>> print s.decode('gbk')
欢迎


2

程序有一段从输入得到的 cp936 编码文字,如 '\xd5\xc5\xc8\xfd'(张三),和从数据库取出的 unicode 字符串,如 u'\u597d\u4eba'(好人),两者需要做拼接时:


>>> a = '\xd5\xc5\xc8\xfd'
>>> b = u'好人'
>>> a + b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd5 in position 0: ordinal not in range(128)


抛出了经常发生的 UnicodeDecodeError 异常。这是因为在 str 和 unicode 做 + 操作时,会自动将 str 转成 unicode,并且使用了 ascii 编码。同样的问题也会发生在对一个 str 对象直接使用 encode 的时候。比如:


>>> a = '\xd5\xc5\xc8\xfd'
>>> a.encode('utf-8')


原因也是一样,encode 是 unicode 类型的方法,对 str 进行调用时,程序会默认先直接试图用 ascii 编码把 str 转成 unicode。


正确的做法:


>>> a.decode('cp936') + b
u'\u5f20\u4e09\u597d\u4eba'
>>> a.decode('cp936').encode('utf-8')
'\xe5\xbc\xa0\xe4\xb8\x89'


另外还有个要注意的地方是,Python 代码的 py 文件默认是是用 ascii 编码,所以在程序里有中文的时候,需要在文件开头指定编码,例如:


# coding: utf-8


有些 IDE 比如 PyScripter 会另外设置你的文件编码,有时还会有冲突。


要注意搞清:


程序文件本身的编码 - 你在程序里赋值的字符串

输入来源的编码 - 获取的变量值

输出环境的编码 - 控制台、文件、网页


最好能保证这几个的一致性,不一致时也要做好相应的转换,才能避免掉进编码的坑。

相关文章
|
3月前
|
存储 Python
Python文件编码概念详解
Python文件编码概念详解
31 1
|
3月前
|
机器学习/深度学习 运维 Python
python深度学习实现自编码器Autoencoder神经网络异常检测心电图ECG时间序列
python深度学习实现自编码器Autoencoder神经网络异常检测心电图ECG时间序列
|
1月前
|
Python
11个提升Python列表编码效率的高级技巧
Python中关于列表的一些很酷的技巧
29 1
|
1月前
|
存储 缓存 Python
python中小数据池和编码
python中小数据池和编码
37 3
|
1月前
|
缓存 Java Unix
python中内存管理等10个编码习惯
【7月更文挑战第3天】本文涵盖了Python编程中的变量管理、模块导入、命令行参数、内存管理和面向对象设计的10个关键概念。
32 0
python中内存管理等10个编码习惯
|
2月前
|
自然语言处理 Python
Python编码问题
Python编码问题是指在处理文本时,由于编码不一致导致程序不能正确处理文本的问题。在Python中,编码问题主要有两种情况:文件编码问题和字符串编码问题。
38 7
|
1月前
|
数据处理 开发者 Python
别再盲目编码!一文读懂Python线程与进程的使用场景与限制,助你成为并发编程高手!
【7月更文挑战第8天】Python并发编程提升效率,关键在于理解线程和进程的适用场景。I/O密集型任务如Web服务器适合用线程,示例展示了使用`threading`处理HTTP请求。CPU密集型任务则利用`multiprocessing`创建进程,绕过GIL限制,实现多核利用。注意线程的GIL限制和进程的开销,选择合适模型以优化并发性能。
26 0
|
2月前
|
Python IDE 开发工具
【Python贪吃蛇】:编码技巧与游戏设计的完美结合
【Python贪吃蛇】:编码技巧与游戏设计的完美结合
|
3月前
|
存储 Python
Python中文编码
Python中文编码
|
3月前
|
Python
python编码和解码
【5月更文挑战第8天】
25 4