UP | HOME

Python 编码问题

目录

1 Unicode & UTF-8

  • Unicode, 一个字符集, 将世界上所有的字符都纳入其中, 并给予每一个字符一个独一无二的编码。

    表示方式: U+XXXX, X 代表一个十六进制数字。

    注: Unicode 只是一个字符集, 只规定了一个字符的二进制编码, 没有规定这个二进制编码的储存方式。

  • UTF-8, Unicode一种 实现方式, 规定了二进制编码的储存方式, 可以由计算机识别。

PS: 不同的编码方式对同一字符的编码很有可能是不一样的, 所以按照错误的编码方式显示数据时, 会导致乱码。

2 编码格式转化

  1. 目前存在有很多种编码方式, 如 UTF-8, cp936
  2. 很多编码方式都向下兼容 ASCII
  3. 不同编码方式在其他字符的表示上很可能不兼容
  4. 我们可以用 Unicode 来映射不同字符集中的字符编码
  5. 因此, 我们可以将一个编码方式 解码Unicode, 对应 Pythondecode()
  6. 然后, 通过 Unicode 映射出另一编码方式中的字符的编码, 对应 Pythonencode()

3 Python2

Python2 中的两种字符串数据类型:

  • str, 存储 bytes, 对应编码方式中 二进制编码 的存储格式
  • unicode, 存储 Unicode 编码, 如: u'\u7f16\u7801' 对应 U+7F16 - 编, U+7801 - 码

注: 类似 u'abcd' 的字符串是一个 Unicode 对象, 之前我一直认为还是 str.

两者的转化方式为:

str -> decode -> unicode
unicode -> encode -> str
>>> s = '编码'
>>> type(s)
<type 'str'>
>>> s.decode('cp936')  # 解码
u'\u7f16\u7801'
>>> type(s.decode('cp936'))
<type 'unicode'>
>>> u'编码'
u'\u7f16\u7801'
>>> u'编码'.encode('cp936')  # 编码
'\xb1\xe0\xc2\xeb'

Python2 中, 组合 strunicode 对象的时候, 会自动尝试将 str 解码为 unicode, 解码中使用的字符集的值与 sys.getdefaultencoding() 的值相等。

然而, 这个值通常为 ascii, 如果你的 str 中包含非 str 字符的话, 就会出现 UnicodeDecodeError

>>> u'abc' + 'def'
u'abcdef'
>>> u'abc' + '编码'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xb1 in position 0: ordinal not in range(128)

4 Python3

Python2 中不同的是, Python3 默认的字符串类型变为了 unicode, 而字节字符串的定义需要通过 b'abc' 的方式。

这是一个坑, 很多时候, Python2 的程序中会存在类似 str.decode() 的代码, 然后到了 Python3, 你就会发现, GG 了。

unicode 是不存在 decode 方法的, unicode 很明显已经不能再解码了, 应该 encode.

同时, 不在对 bytesunicode 进行隐式处理。

所以, 直接组合 unicodebytes 将会报错。

>>> s = '编码'
>>> type(s)
<class 'str'>
>>> s = u'编码'
>>> type(s)
<class 'str'>
>>> s = b'编码'
  File "<stdin>", line 1
SyntaxError: bytes can only contain ASCII literal characters.
>>> s = '编码'.encode('cp936')
>>> s
b'\xb1\xe0\xc2\xeb'
>>> type(s)
<class 'bytes'>
>>> s = '编码'.decode('cp936')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'decode'

5 编写程序

无法改变的五个事实:

  1. 程序的所有输入输出均为 byte(二进制数据)
  2. 世界上的文本需要比 256 更多的符号来表现
  3. 你的程序必须处理 byteunicode
  4. byte 流中不会包含编码信息
  5. 指明的编码有可能是错误的

编程建议:

  1. Unicode 三明治: 尽可能的让程序处理的文本都为 Unicode
  2. 了解程序中的字符串, 哪些是 unicode, 哪些是 byte, 对于这些 byte 串,知道他们的编码是什么
  3. 测试 Unicode 支持。使用一些奇怪的符号来测试是否已经做到了以上几点

6 代码样例

在阅读 webpy 源码的过程中, 发现了在 utils.py 中的一段处理这个问题的代码, 感觉很有用:

def safeunicode(obj, encoding='utf-8'):
    r"""
    Converts any given object to unicode string.

        >>> safeunicode('hello')
        u'hello'
        >>> safeunicode(2)
        u'2'
        >>> safeunicode('\xe1\x88\xb4')
        u'\u1234'
    """
    t = type(obj)
    if t is text_type:
        return obj
    elif t is bytes:
        return obj.decode(encoding)
    elif t in [int, float, bool]:
        return unicode(obj)
    #elif hasattr(obj, '__unicode__') or isinstance(obj, unicode):
    #    return unicode(obj)
    #else:
    #    return str(obj).decode(encoding)
    else:
        return unicode(obj)

def safestr(obj, encoding='utf-8'):
    r"""
    Converts any given object to utf-8 encoded string. 

        >>> safestr('hello')
        'hello'
        >>> safestr(2)
        '2'
    """

    if PY2 and isinstance(obj, unicode):
        return obj.encode(encoding)
    elif is_iter(obj):
        return imap(safestr, obj)
    else:
        return str(obj)

if not PY2:
    #Since Python3, utf-8 encoded strings and unicode strings are the same thing
    safeunicode = safestr

笔记链接: function safeunicode

7 参考链接

版权声明:本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可