得之我幸 失之我命

when someone abandons you,it is him that gets loss because he lost someone who truly loves him but you just lost one who doesn’t love you.

Python2 和 3 的编码问题

无论是 py2 还是 py3 都可以理解成是 Unicode 编码,但是在电脑硬盘上存储是按照不同的映射关系的

首先需要清楚的是 python 的 encode 和 decode,在搞清楚这个问题之前,还得知道 Unicode 将所有的字符都对应了相应的码点,而 UTF-8 或者 ASCII 码是对应从 Unicode 到字节的映射方式,既然有映射方式,就有映射方向,从 Unicode 到字节码(byte string)叫 encode,把从字节码(byte string)到 Unicode 码叫 decode。感觉还是不清晰,再普及几个 “字” 字辈的兄弟

上几个 “字” 字辈的家人,最后一位乱入

  1. 字节

    字节(Byte)是计算机中数据存储的基本单元,一字节等于一个 8 位的比特,计算机中的所有数据,不论是保存在磁盘文件上的还是网络上传输的数据(文字、图片、视频、音频文件)都是由字节组成的

  2. 字符

    这篇文章就是由很多个字符(Character)构成的,字符一个信息单位,它是各种文字和符号的统称,比如一个英文字母是一个字符,一个汉字是一个字符,一个标点符号也是一个字符

  3. 字符集

    字符集(Character Set)就是某个范围内字符的集合,不同的字符集规定了字符的个数,比如 ASCII 字符集总共有128个字符,包含了英文字母、阿拉伯数字、标点符号和控制符。而 GB2312 字符集定义了 7445 个字符,包含了绝大部分汉字字符

  4. 字符码

    字符码(Code Point)指的是字符集中每个字符的数字编号,例如 ASCII 字符集用 0-127 连续的 128 个数字分别表示 128 个字符,例如 “A” 的字符码编号就是 65

  5. 字符编码

    字符编码(Character Encoding)是将字符集中的字符码映射为字节流的一种具体实现方案,常见的字符编码有 ASCII 编码、UTF-8 编码、GBK 编码等。某种意义上来说,字符集与字符编码有种对应关系,例如 ASCII 字符集对应 有 ASCII 编码。ASCII 字符编码规定使用单字节中低位的 7 个比特去编码所有的字符。例如 “A” 的编号是 65,用单字节表示就是 0×41,因此写入存储设备的时候就是 b’01000001’

  6. 编码、解码

    编码的过程是将字符转换成字节流,解码的过程是将字节流解析为字符

python2 编码

python2 中,有两种不同的字符串数据类型:

  • str 对象,字节存储

  • 在字符串前使用一个 ’u’ 的前缀,表示的是这个字符的 Unicode 码点

  1. Python2 处理英文字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    >>> str='hello' # 首先 ’hello' 字符串的每个英文字符变换成 unicode 编码,在存储时 python2 对英文按照 ascii 映射的方式编码存储 5 个字节编码
    >>> str
    'hello'
    >>> type(str) # 此时赋值给 str,就是第一种:str 对象,存储着字节
    <type 'str'>

    >>> len(str) # len 字符数,就是一字节一英文字符
    5
    >>> str.decode('ascii') # ascii 解码:按照 ascii 规则把 str 英文字符由字节编码映射成字符编码,也就是把 str 对象类型变为 unicode 字符码
    u'hello'
    >>> str.decode('utf-8') # utf-8 解码:按照 utf-8 规则把 str 英文字符由字节编码映射成字符编码,也就是把 str 对象类型变为 unicode 字符码
    u'hello' # 为什么结果一样,因为对待英文字符 Ascii 和 utf-8 都是按照一个字节存储的,没有什么差别
    >>> print(u'hello') # 关于 print 函数,无论字符 unicode 编码还是字节 ascii 编码都能正常输出
    hello
    >>> print(str)
    hello
  2. Python2 处理中文字符

    1
    2
    3
    4
    5
    6
    7
    8
    # 直接输入
    >>> str="中国" # 中文在 python2 中编码方式看系统,macOS 是 utf-8 编码
    >>> type(str)
    <type 'str'>
    >>> str # str 仍然是一个 str 对象(字节存储)
    '\xe4\xb8\xad\xe5\x9b\xbd'
    >>> len(str) # len 函数处理 str 对象,是统计 str 字节的个数,因为 str 是字节编码的
    6
    1
    2
    3
    4
    5
    6
    7
    8
    9
    u'字符串输入'
    >>> str=u'中国'
    >>> str
    u'\u4e2d\u56fd'
    >>> type(str)
    <type 'unicode'>
    >>> len(str) # len 函数对于 unicode 编码,按照字符个数算
    2
    # 字符串前加个 u,变量按照 unicode 存储的,两个字节一个中文/英文字符,对于 python2,建议在输入字符串加个 u,按照 unicode 编码存储
  3. Python2 开头为啥写 #coding=utf-8

    Python 文件编译最终还是要转换成字节码,Python2 开头写 #coding=utf-8 其实就是把这个 Python 文件按照 utf-8 编码的方式映射到字节码,如果不加这个开头,代码里面的中文会按照 Python 默认的 ascii 码方式 encode。加了这个开头之后,程序里面的字符都将会使用 utf-8 编码的方式被映射到字节码,也就是上一个大节里面的 byte string,值得注意的是,程序中的汉字将会以 utf-8 的形式编码成为字节码,因此如果需要将其 decode 到 Unicode 字符,也是需要使用 utf-8 方式 decode

    但是,这个开头并没有改变 Python 的系统编码方式:Python2 默认的编码解码方式是 ascii

    1
    2
    3
    4
    5
    6
    # 错误的 test.py
    #coding=utf-8 # 中文赋值按照 utf-8 码方式 encode
    s='中国'
    s_decode=s.decode('utf-8')
    ps=s_decode+s # 字节编码与字符编码拼接最后是字符编码,所以 s 字节编码需要解码,这时按照 ascii
    print ps # 中文不能被 ascii 解码,出错
    1
    2
    3
    4
    $ python test.py
    Traceback (most recent call last):
    ps=s_decode+s
    UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

    修复这个问题的方法有下面两种

    1
    2
    3
    4
    5
    6
    >>> import sys
    >>> reload(sys)
    <module 'sys' (built-in)>
    >>> sys.setdefaultencoding('utf-8') # 将 Python 的编码默认编码方式由 ascii 码换成了 utf-8 编码,因此 s_decode+s 这个就不像上面一样报错了
    >>> s_decode+s
    u'\u4e2d\u56fd\u4e2d\u56fd'
    1
    2
    3
    4
    5
    6
    # 正确的 test.py
    #coding=utf-8 # 中文赋值按照 utf-8 码方式 encode
    s='中国'
    s_decode=s.decode('utf-8')
    ps=s_decode+s.decode('utf-8')
    print ps
    1
    2
    $ python test.py
    中国中国

python3 编码

Python 3 也有两种类型,一个是 str(unicode), 一个是 byte 码

Python 3 中对 Unicode 支持的最大变化就是没有对 byte 字符串的自动解码

如果你想要用一个 byte 字符串和一个 unicode 相连接的话,你会得到一个错误,不管包含的内容是什么。可以简单理解为: python2 中的 unicode -> python3 的 str,python2 中的 str -> python3 的 byte

  1. python3 处理中文

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
        >>> s="中"
    >>> type(s)
    <class 'str'>
    >>> s
    '中'
    >>> len(s) # s 是 unicode 编码
    1
    >>> s.encode('utf-8')
    b'\xe4\xb8\xad'

    '''任何中文的 unicode 编码,都不能用 ascii 编码/解码'''
    >>> s.encode('ascii')
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    UnicodeEncodeError: 'ascii' codec can't encode character '\u4e2d' in position 0: ordinal not in range(128)
    >>> s.decode('ascii')
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    AttributeError: 'str' object has no attribute 'decode'

    >>> s.encode('utf-8')
    b'\xe4\xb8\xad'
    >>> type(s1)
    <class 'bytes'>
    >>> s.encode('utf-8').decode('utf-8') # 只要是 unicode(str 对象)就可以直接输出
    ''

    '''对于 byte.decode() 解码:python3 不支持二进制形式解码,python2 支持'''
    # python3
    >>>'\xe4\xb8\xad'.decode('utf-8')
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    AttributeError: 'str' object has no attribute 'decode'

    # python2
    >>> '\xe4\xb8\xad'.decode('utf-8')
    u'\u4e2d'
  2. print

    • 对于 byte 字节

      二进制形式输出:不加 b,乱码;加 b,当字符串输出

      用变量存储输出:二进制输出

    • 对于 unicode(str)

      直接输出汉字/英文

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    >>> print('\xe4\xb8\xad') # utf-8
    ä¸
    >>> print(b'\xe4\xb8\xad')
    b'\xe4\xb8\xad'
    >>> print (s) # s:unicode

    >>> print(s1) # s1 变量,utf-8 存储
    b'\xe4\xb8\xad'
    >>> s2=s.encode('gbk')
    >>> s2
    b'\xd6\xd0'
  3. python3 处理英文字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    >>> s='dv'
    >>> s
    'dv'
    >>> len(s)
    2
    >>> s.encode('utf-8')
    b'dv'
    >>> s1=s.encode('utf-8')
    >>> s2=s+s1
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: can only concatenate str (not "bytes") to str
    >>> s3=s+s1.decode('utf-8')
    >>> s3
    'dvdv'

编码历史

  1. ASCII 编码

    最早出现的是 ASCII 码,使用 8 位二进制数组合表示 128 种字符。因为 ASCII 编码是美国人发明的,当初没考虑给别的国家用,所以,它仅仅表示了所有美式英语的语言字符。但是没有使用完

  2. ISO 8859-1/windows-1252

    128 位字符满足了美国人的需求,但是随之欧洲人加入互联网,为了满足欧洲人的需求,8 位二进制后面还有 128 位。这一段编码称之扩展字符集,即 ISO 8859-1 编码标准,后来欧洲的需求变更,即规定了 windows-1252 代替了 ISO 8859-1

  3. GB2312

    当我国加入后,8 位二进制(即一个字节)用完了,于是保留 ASCII 编码即前 128 位,后面的全部删除。因为我国的语言博大精深,所以需要 2 个字节,即 16 位才能满足需求,所以当计算机遇到大于 127 的字节时,就一次性读取两个字节,解码成汉字。即 GB2312 编码

  4. GBK

    相当于 GB2312 的改进版,增添了中文字符。但还是 2 个字节表示汉字

  5. GB18030

    为了满足日韩和我国的少数民族的需求,对 GBK 的改进,使用变长编码,要么使用两个字节,要么使用四个字节

  6. Unicode

    虽然每种编码都兼容 ASCII 编码,但是各个国家是不兼容的。于是出现了 Unicode,它将所有的编码进行了统一。它不能算是一种具体的编码标准,只是将全世界的字符进行了编号,并没有指定他们具体在计算机中以什么样的形式存储。它的具体实现有UTF-8、UTF-16、UTF-32等

be slow to promise and quick to perform.