zoukankan      html  css  js  c++  java
  • Python深入:编码问题总结

    一:字符编码简介

             1:ASCII

             最初的计算机的使用是在美国,所用到的字符也就是现在键盘上的一些符号和少数儿个特殊的符号,一个字节所就能足以容纳所有的这些字符,实际上表示这些字符的字节最高位都为0,也就是说这些字节都在0到127之间,如字符a对应数字97。这套编码规则被称为ASCII(美国标准信息交换码)。

     

             2:GBK、GB2312

             随着计算机的应用和普及,许多国家都把本地的字符集引入了计算机,大大扩展了计算机中字符的范围。以中文为例,一个字节是不能容纳所有的中文汉字的,因此大陆将每一个中文字符都用两个字节的数字来表示,原有的ASCII字符的编码保持不变,仍用一个字节表示,为了将一个中文字符与两个ASCII码字符相区别,中文字符的每个字节的最高位都为1,这套编码规则称为GBK(国标码),后来,又在GBK的基础上对更多的中文字符(包括繁体)进行了编码,新的编码系统就是GB2312,可见GBK是GB2312的子集。

             3:Unicode

             每个国家和地区都制定了一套自己的编码,那么同样的一个字节,在不同的国家和地区就代表了不同的字符。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。

             为了解决各个国家和地区使用本地化字符编码带来的不利影响,将全世界所有的符号进行了统一编码,称之为Unicode编码。这是一种所有符号的编码。如 “中”这个符号,在全世界的任何角落始终对应的都是一个十六进制的数字4e2d。

             最初的Unicode标准UCS-2使用两个字节表示一个字符,所以你常常可以听到Unicode使用两个字节表示一个字符的说法。但过了不久有人觉得256*256太少了,还是不够用,于是出现了UCS-4标准,它使用4个字节表示一个字符,不过我们用的最多的仍然是UCS-2。

     

             4:UTF

             注意,UCS(Unicode Character Set)还仅仅是字符对应码位的一张表而已,比如"中"这个字的码位是4E2D。字符具体如何传输和储存则是由UTF(UCS Transformation Format)来负责。

             一开始这事很简单,直接使用UCS的码位来保存,这就是UTF-16,比如,"汉"直接使用x4Ex2D保存(UTF-16-BE),或是倒过来使用x2Dx4E保存(UTF-16-LE)。

             这里就有两个严重的问题,第一个问题是,如何才能区别Unicode和ASCII?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?第二个问题是,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用两个字节表示,这对于存储来说是极大的浪费。于是UTF-8横空出世。

     

             5:UTF-8

             UTF-8是使用最广的一种Unicode的实现方式。其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。

             UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8的编码规则很简单,只有二条:

             1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

             2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

             下表总结了编码规则,字母x表示可用编码的位。

    Unicode符号范围

     UTF-8编码方式

    00 00 00 00   -   00 00 00 7F

     0xxxxxxx

    00 00 00 80   -   00 00 07 FF

     110xxxxx 10xxxxxx

    00 00 08 00   -   00 00 FF FF

     1110xxxx 10xxxxxx 10xxxxxx

    00 01 00 00   -   00 10 FF FF

     11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

             跟据上表,解读UTF-8编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

             下面以汉字"严"为例,演示如何实现UTF-8编码。

             已知"严"的unicode是4E25(1001110 00100101),根据上表,可以发现4E25处在第三行的范围内(00000800-0000 FFFF),因此"严"的UTF-8编码需要三个字节,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"严"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,"严"的UTF-8编码是"11100100 10111000 10100101",转换成十六进制就是E4B8A5。

     

             6:BOM

             BOM(Byte Order Mark)。UTF引入了BOM来表示自身编码,如果一开始读入的几个字节是其中之一,则代表接下来要读取的文字使用的编码是相应的编码:

    BOM_UTF8             'xefxbbxbf' 

    BOM_UTF16_LE    'xffxfe' 

    BOM_UTF16_BE    'xfexff'

             如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。以汉字"严"为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。

             一般情况下,不建议在Linux中使用BOM。

     

             7:Unicode与UTF-8之间的转换

             "严"的Unicode码是4E25,UTF-8编码是E4B8A5,以该汉字为例,利用Ultraedit查看各种编码的具体值,一般的编辑器都支持以不同的编码方式保存文本,编码方式如下:

             1)ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。这种方式编码的文件,就是两个字节"D1 CF",这正是"严"的GB2312编码:

             2)UTF16编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码。编码是四个字节"FF FE 25 4E",其中"FF FE"表明是小头方式存储:

             3)UTF16-NOBOM编码与上一个选项相对应,只不过没有BOM编码,也就是文件中只存储UTF16编码"25 4E":

             4)UTF-8编码,文件中共6个字节:"EF BB BF E4 B8 A5",其中前三个为BOM编码,后三个为UTF8编码:

             6)UTF8-NOBOM编码与上一个选项相对应,只不过没有BOM编码,也就是文件中只存储UTF8编码" E4 B8 A5":

     

    二:Python代码文件的编码

             Python解释器会使用某种编码方式来解释Python源代码文件,默认情况下,这种编码方式就是ASCII。

             Python2.1版本,在Python源码文件中,只能以以基于Latin-1的“转义unicode”的方式来书写Unicode字符,这对于亚洲的程序员是很不友好的。解决该问题的方法是,在源码文件的顶部,使用某种特殊的注释方式来表明源码文件的编码。

             为了表明源码文件的编码,这种特殊的注释必须位于源码文件的第一行或第二行,类似于:

    #coding=<encodingname>  

    或:

    #!/usr/bin/python
    # -*- coding:<encoding name> -*-

     或:

    #!/usr/bin/python
    # vim: setfileencoding=<encoding name> :

             这种方式的本质是:文件的第一行或第二行必须能匹配正则表达式:

    ” coding[:=]s*([-w.]+)”,该表达式中的group1就会被解释为编码名称,如果Python无法识别该编码,则在编译时就会报错。

             像Windows这样的平台,会在Unicode文件的最开始加上BOM字节码,UTF-8文件的字节码是:”xefxbbxbf”。为了兼容这种方式,包含这种字节码的文件,即使没有字节编码注释,也会被解释为”utf-8”。如果一个源码文件,既有编码注释,又有UTF-8的BOM字节码,则在编码注释中的编码名称只能是”utf-8”(”utf8”都不行),否则会报错: “SyntaxError: encodingproblem: utf-8”。

            

             下面是一些使用编码注释的例子:

             1. Emacs风格的文件编码注释:

    #!/usr/bin/python
    # -*- coding: latin-1 -*-
    import os, sys
     ...
    
    
    #!/usr/bin/python
    # -*- coding: iso-8859-15 -*-
    import os, sys
    ...
    #!/usr/bin/python
    # -*- coding: ascii -*-
    import os, sys
    ...

             2. 使用纯文本方式的注释:

    # This Python file uses the following encoding:utf-8
    import os, sys
    ...

             3. 没有编码注释,则Python解释器默认使用ASCII。

             如果没声明编码,但是文件中又包含非ASCII编码的字符的话,python解析器去解析的python文件,就会报错。

    #!/usr/local/bin/python
    import os, sys
    ...

             4. 下面这些语法不起作用

             没有加”coding:”前缀:

    #!/usr/local/bin/python
    # latin-1
    import os, sys
    ...

             编码方式不在第一行或第二行:

    #!/usr/local/bin/python
    #
    # -*- coding: latin-1 -*-
    import os, sys
    ...

             不支持的编码方式:

    #!/usr/local/bin/python
    # -*- coding: utf-42 -*-
    import os, sys
    ...

              5:注意:

             允许的编码方式包括ASCII兼容编码以及某些多字节编码,比如SHIFT_JIS。但不包括为所有字符都是有双字节或者更多字节的编码,比如UTF-16(注:也就是通常说的Unicode,但SHIFT_JIS也好,GBK也好,因为兼容ASCII编码,所以都可以在Python源文件里使用)。

             如果声明的编码与实际不符(就是说,文件实际上是以另外的编码保存的),出错的可能性很大。

     

             文件的编码格式决定了在该源文件中声明的字符串的编码:

    str = '哈哈'
    print repr(str)

    a.如果文件格式为utf-8,则str的值为:'xe5x93x88xe5x93x88'(哈哈的utf-8编码)

    b.如果文件格式为gbk,则str的值为:'xb9xfexb9xfe'(哈哈的gbk编码)

     

    三:str和unicode

             str和unicode都是basestring的子类。编码是指unicode-->str,解码是指str-->unicode。

             str是一个字节数组,这个字节数组表示的是对unicode对象编码(可以是utf-8、gbk、cp936、GB2312)后的存储的格式。这里它仅仅是一个字节流,没有其它的含义,如果你想使这个字节流显示的内容有意义,就必须用正确的编码格式,解码显示。 对UTF-8编码的str'哈哈'使用len()函数时,结果是6,因为实际上,UTF-8编码的'哈哈' == 'x e5x93x88xe5x93x88'。

             unicode才是真正意义上的字符串,对字节串str使用正确的字符编码进行解码后获得,例如'哈哈'的unicode对象为 u'u54c8u54c8' ,len(u”哈哈”) == 2

             字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。 

             decode的作用是将其他编码的字符串转换成unicode编码,如str1.decode('gb2312'),表示将gb2312编码的字符串str1转换成unicode编码。 

             encode的作用是将unicode编码转换成其他编码的字符串,如str2.encode('gb2312'),表示将unicode编码的字符串str2转换成gb2312编码。 

             因此,转码的时候一定要先搞明白,字符串str是什么编码,然后decode成unicode,然后再encode成其他编码

     

    str.decode([encoding[, errors]])

             使用encoding指示的编码,对str进行解码,返回一个unicode对象。默认情况下encoding是“字符串默认编码”,比如ascii。

    errors指示如何处理解码错误,默认情况下是”strict”,也就是遇到解码错误时,直接抛出UnicodeError异常。其他的errors值可以有”ignore”,”replace”等。

     

    unicode(object[, encoding[, errors]])

             与str.decode作用相同,但是该方法要快一些:http://stackoverflow.com/questions/440320/unicode-vs-str-decode-for-a-utf8-encoded-byte-string-python-2-x

     

    str.encode([encoding[, errors]])

             返回一个经encoding编码后的str对象,默认的encoding是”默认字符串编码”。

    errors指示如何处理解码错误,默认情况下是”strict”,也就是遇到解码错误时,直接抛出UnicodeError异常。其他的errors值可以有”ignore”,”replace”,'xmlcharrefreplace', 'backslashreplace'等。

     

             一般情况下,对Unicode对象进行编码encode(),返回str对象。对str对象调用解码decode(),返回unicode对象。

             但是对str调用encode也不会报错,str.encode()实际上就等价于str.decode(sys.defaultencoding).encode().而sys.defaultencoding一般是ascii。

             同样的,对unicode对象进行解码unicode.decode实际上就等价于unicode.encode(sys.defaultencoding).decode()

     

    四:示例

             英文字符的ASCII、UTF-8、GBK等编码都是一样的,因此下面的示例仅讨论汉字的情况。并且保证源码文件的实际编码方式,和编码注释是一样的。

             1:文件为默认编码(ASCII),无编码注释

    astr ="哈哈"

            运行文件,报错:SyntaxError: Non-ASCII character 'xb9' in file 2.py on line 2, butno encoding declared; seehttp://www.python.org/peps/pep-0263.htmlfor details

             原因:默认的文件编码为ASCII,这种编码无法处理汉字,将文件保存为GBK或者UTF-8格式,并相应的注释声明。

     

             2:文件编码为UTF-8

    # -*- coding:UTF-8 -*-
    def toHexString(s):
        return ":".join("{0:x}".format(ord(c))for c in s)
     
    ustr =u"哈哈"
    print repr(ustr)
    print ustr, "len is ",len(ustr)
    print
     
    astr ="哈哈"
    ustr =astr.decode("UTF-8")
    print repr(ustr)
    print ustr, "len is ",len(ustr)

             运行文件,结果为:

    u'u54c8u54c8'
    哈哈 len is  2
    
    u'u54c8u54c8'
    哈哈 len is  2

      

             可见,上下两种方式,其实是等价的,在源码文件中直接使用u”...”编写Unicode字符串,使用文件的注释编码,将该str解码为Unicode:

             Python supports writing Unicode literals in anyencoding, but you have to declare the encoding being used. This is done byincluding a special comment as either the first or second line of the sourcefile。

    (https://docs.python.org/2/howto/unicode.html#the-unicode-type)

     

             3:文件为UTF-8编码

    # -*- coding:utf-8 -*-
     
    astr ="哈哈"
    print repr(astr)
    print astr, "len is ",len(astr)
    print
     
    ustr =astr.decode("gbk")
    print repr(ustr)
    print ustr, "len is ",len(ustr)

             运行文件,结果如下:

    'xe5x93x88xe5x93x88'
    鍝堝搱 len is  6

             (第一行输出:因为文件为UTF-8编码,所以输出”哈哈”的UTF-8编码)

             (第二行输出:因为print语句它的实现是将要输出的内容传送给终端,终端会根据默认的编码(GBK)对输入的字节流进行解释,因为 'xe5x93x88xe5x93x88'用GBK去解释,其显示的出来就是“鍝堝搱”,而且,该str的长度就是编码的长度,为6)

     

    u'u935du581du6431'
    鍝堝搱 len is  3

             (第一行输出:因为文件是UTF-8编码,但是却用GBK对“哈哈”,也就是'xe5x93x88xe5x93x88'进行解码,所以输出错误)

             (第二行输出:Python在向控制台输出unicode对象的时候会自动根据输出环境的编码(GBK)进行转换,而如果输出的不是unicode对象而是普通的str字符串,则会直接按照该字符串的编码输出字符串(http://noalgo.info/578.html)。所以,printustr就等价于print astr了。如果将ustr = astr.decode("gbk") 替换成 ustr =astr.decode("UTF-8"),则输出:

    u'u54c8u54c8'
    哈哈 len is  2
    54c8:54c8

             这是因为将astr按照文件编码注释声明的编码方式进行解码,解码成Unicode之后,print时又自动进行GBK编码,所以会输出正确的字符)

     

             4:打开具有汉字的文件:

             文件名可以包含Unicode字符,操作系统在处理这样的文件时,将Unicode字符根据某种编码进行处理,比如Max OS X使用UTF-8编码,而Windows上的编码是可配置的,用”mbcs”来表示当前的编码。

             sys.getfilesystemencoding()函数可以得到当前文件系统使用的编码,但是其实可以不用管这些,当需要打开一个包含汉字的文件时,直接使用Unicode字符即可,这样它会自动转换为正确的编码字符串:

    # -*- coding:UTF-8 -*-
     
    #astr = "哈哈.txt".decode("UTF-8")
    #astr = u"哈哈.txt"
    astr ="哈哈.txt".decode("UTF-8").encode("GBK")
    try:
        with open(astr) asfobj:
            print fobj.read()
    except Exception, e:
        print "error is ",e

             上面这三种方式,都可以正确打开该文件。

     

    参考:

    http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

    http://www.cnblogs.com/huxi/articles/1897271.html

    https://www.python.org/dev/peps/pep-0263/

    http://www.crifan.com/files/doc/docbook/char_encoding/release/html/char_encoding.html

    http://www.jb51.net/article/26543.htm

    http://www.jb51.net/article/17560.htm

  • 相关阅读:
    CSS选择器
    CSS样式与语法
    实训第二天
    初学前端
    html-路径和图像标签
    html
    JavaScript(基础)
    CSS(2)
    CSS()
    HTML(链接与表单)
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247143.html
Copyright © 2011-2022 走看看