Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 299|回复: 0

[默认分类] Python字符编码详解

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-7-8 16:01:26 | 显示全部楼层 |阅读模式
    本文简单介绍了各种常用的字符编码的特点,并介绍了在python2.x中如何与编码问题作战 :)
    请注意本文关于Python的内容仅适用于2.x,3.x中str和unicode有翻天覆地的变化,请查阅其他相关文档。
    尊重作者的劳动,转载请注明作者及原文地址 >.<
    1. 字符编码简介
    1.1. ASCII
    ASCII(American Standard Code for Information Interchange),是一种单字节的编码。计算机世界里一开始只有英文,而单字节可以表示256个不同的字符,可以表示所有的英文字符和许多的控制符号。不过ASCII只用到了其中的一半(\x80以下),这也是MBCS得以实现的基础。
    1.2. MBCS
    然而计算机世界里很快就有了其他语言,单字节的ASCII已无法满足需求。后来每个语言就制定了一套自己的编码,由于单字节能表示的字符太少,而且同时也需要与ASCII编码保持兼容,所以这些编码纷纷使用了多字节来表示字符,如GBxxx、BIGxxx等等,他们的规则是,如果第一个字节是\x80以下,则仍然表示ASCII字符;而如果是\x80以上,则跟下一个字节一起(共两个字节)表示一个字符,然后跳过下一个字节,继续往下判断。
    这里,IBM发明了一个叫Code Page的概念,将这些编码都收入囊中并分配页码,GBK是第936页,也就是CP936。所以,也可以使用CP936表示GBK。
    MBCS(Multi-Byte Character Set)是这些编码的统称。目前为止大家都是用了双字节,所以有时候也叫做DBCS(Double-Byte Character Set)。必须明确的是,MBCS并不是某一种特定的编码,Windows里根据你设定的区域不同,MBCS指代不同的编码,而Linux里无法使用MBCS作为编码。在Windows中你看不到MBCS这几个字符,因为微软为了更加洋气,使用了ANSI来吓唬人,记事本的另存为对话框里编码ANSI就是MBCS。同时,在简体中文Windows默认的区域设定里,指代GBK。
    1.3. Unicode
    后来,有人开始觉得太多编码导致世界变得过于复杂了,让人脑袋疼,于是大家坐在一起拍脑袋想出来一个方法:所有语言的字符都用同一种字符集来表示,这就是Unicode。
    最初的Unicode标准UCS-2使用两个字节表示一个字符,所以你常常可以听到Unicode使用两个字节表示一个字符的说法。但过了不久有人觉得256*256太少了,还是不够用,于是出现了UCS-4标准,它使用4个字节表示一个字符,不过我们用的最多的仍然是UCS-2。
    UCS(Unicode Character Set)还仅仅是字符对应码位的一张表而已,比如"汉"这个字的码位是6C49。字符具体如何传输和储存则是由UTF(UCS Transformation Format)来负责。
    一开始这事很简单,直接使用UCS的码位来保存,这就是UTF-16,比如,"汉"直接使用\x6C\x49保存(UTF-16-BE),或是倒过来使用\x49\x6C保存(UTF-16-LE)。但用着用着美国人觉得自己吃了大亏,以前英文字母只需要一个字节就能保存了,现在大锅饭一吃变成了两个字节,空间消耗大了一倍……于是UTF-8横空出世。
    UTF-8是一种很别扭的编码,具体表现在他是变长的,并且兼容ASCII,ASCII字符使用1字节表示。然而这里省了的必定是从别的地方抠出来的,你肯定也听说过UTF-8里中文字符使用3个字节来保存吧?4个字节保存的字符更是在泪奔……(具体UCS-2是怎么变成UTF-8的请自行搜索)
    另外值得一提的是BOM(Byte Order Mark)。我们在储存文件时,文件使用的编码并没有保存,打开时则需要我们记住原先保存时使用的编码并使用这个编码打开,这样一来就产生了许多麻烦。(你可能想说记事本打开文件时并没有让选编码?不妨先打开记事本再使用文件 -> 打开看看)而UTF则引入了BOM来表示自身编码,如果一开始读入的几个字节是其中之一,则代表接下来要读取的文字使用的编码是相应的编码:
    BOM_UTF8 "\xef\xbb\xbf"
    BOM_UTF16_LE "\xff\xfe"
    BOM_UTF16_BE "\xfe\xff"
    并不是所有的编辑器都会写入BOM,但即使没有BOM,Unicode还是可以读取的,只是像MBCS的编码一样,需要另行指定具体的编码,否则解码将会失败。
    你可能听说过UTF-8不需要BOM,这种说法是不对的,只是绝大多数编辑器在没有BOM时都是以UTF-8作为默认编码读取。即使是保存时默认使用ANSI(MBCS)的记事本,在读取文件时也是先使用UTF-8测试编码,如果可以成功解码,则使用UTF-8解码。记事本这个别扭的做法造成了一个BUG:如果你新建文本文件并输入"姹塧"然后使用ANSI(MBCS)保存,再打开就会变成"汉a",你不妨试试 :)
    2. Python2.x中的编码问题
    2.1. str和unicode
    str和unicode都是basestring的子类。严格意义上说,str其实是字节串,它是unicode经过编码后的字节组成的序列。对UTF-8编码的str"汉"使用len()函数时,结果是3,因为实际上,UTF-8编码的"汉" == "\xE6\xB1\x89"。
    unicode才是真正意义上的字符串,对字节串str使用正确的字符编码进行解码后获得,并且len(u"汉") == 1。
    再来看看encode()和decode()两个basestring的实例方法,理解了str和unicode的区别后,这两个方法就不会再混淆了:
    1. # coding: UTF-8
    2. u = u"汉"
    3. print repr(u) # u"\u6c49"
    4. s = u.encode("UTF-8")
    5. print repr(s) # "\xe6\xb1\x89"
    6. u2 = s.decode("UTF-8")
    7. print repr(u2) # u"\u6c49"
    8. # 对unicode进行解码是错误的
    9. # s2 = u.decode("UTF-8")
    10. # 同样,对str进行编码也是错误的
    11. # u2 = s.encode("UTF-8")
    复制代码
    需要注意的是,虽然对str调用encode()方法是错误的,但实际上Python不会抛出异常,而是返回另外一个相同内容但不同id的str;对unicode调用decode()方法也是这样。很不理解为什么不把encode()和decode()分别放在unicode和str中而是都放在basestring中,但既然已经这样了,我们就小心避免犯错吧。
    2.2. 字符编码声明
    源代码文件中,如果有用到非ASCII字符,则需要在文件头部进行字符编码的声明,如下:
    1. #-*- coding: UTF-8 -*-
    复制代码
    实际上Python只检查#、coding和编码字符串,其他的字符都是为了美观加上的。另外,Python中可用的字符编码有很多,并且还有许多别名,还不区分大小写,比如UTF-8可以写成u8。参见http://docs.python.org/library/codecs.html#standard-encodings
    另外需要注意的是声明的编码必须与文件实际保存时用的编码一致,否则很大几率会出现代码解析异常。现在的IDE一般会自动处理这种情况,改变声明后同时换成声明的编码保存,但文本编辑器控们需要小心 :)
    2.3. 读写文件
    内置的open()方法打开文件时,read()读取的是str,读取后需要使用正确的编码格式进行decode()。write()写入时,如果参数是unicode,则需要使用你希望写入的编码进行encode(),如果是其他编码格式的str,则需要先用该str的编码进行decode(),转成unicode后再使用写入的编码进行encode()。如果直接将unicode作为参数传入write()方法,Python将先使用源代码文件声明的字符编码进行编码然后写入。
    1. # coding: UTF-8
    2. f = open("test.txt")
    3. s = f.read()
    4. f.close()
    5. print type(s) # <type "str">
    6. # 已知是GBK编码,解码成unicode
    7. u = s.decode("GBK")
    8. f = open("test.txt", "w")
    9. # 编码成UTF-8编码的str
    10. s = u.encode("UTF-8")
    11. f.write(s)
    12. f.close()
    复制代码
    另外,模块codecs提供了一个open()方法,可以指定一个编码打开文件,使用这个方法打开的文件读取返回的将是unicode。写入时,如果参数是unicode,则使用open()时指定的编码进行编码后写入;如果是str,则先根据源代码文件声明的字符编码,解码成unicode后再进行前述操作。相对内置的open()来说,这个方法比较不容易在编码上出现问题。
    1. # coding: GBK
    2. import codecs
    3. f = codecs.open("test.txt", encoding="UTF-8")
    4. u = f.read()
    5. f.close()
    6. print type(u) # <type "unicode">
    7. f = codecs.open("test.txt", "a", encoding="UTF-8")
    8. # 写入unicode
    9. f.write(u)
    10. # 写入str,自动进行解码编码操作
    11. # GBK编码的str
    12. s = "汉"
    13. print repr(s) # "\xba\xba"
    14. # 这里会先将GBK编码的str解码为unicode再编码为UTF-8写入
    15. f.write(s)
    16. f.close()
    复制代码
    2.4. 与编码相关的方法
    sys/locale模块中提供了一些获取当前环境下的默认编码的方法。
    1. # coding:gbk
    2. import sys
    3. import locale
    4. def p(f):
    5.     print "%s.%s(): %s" % (f.__module__, f.__name__, f())
    6. # 返回当前系统所使用的默认字符编码
    7. p(sys.getdefaultencoding)
    8. # 返回用于转换Unicode文件名至系统文件名所使用的编码
    9. p(sys.getfilesystemencoding)
    10. # 获取默认的区域设置并返回元祖(语言, 编码)
    11. p(locale.getdefaultlocale)
    12. # 返回用户设定的文本数据编码
    13. # 文档提到this function only returns a guess
    14. p(locale.getpreferredencoding)
    15. # \xba\xba是"汉"的GBK编码
    16. # mbcs是不推荐使用的编码,这里仅作测试表明为什么不应该用
    17. print r""\xba\xba".decode("mbcs"):", repr("\xba\xba".decode("mbcs"))
    18. #在笔者的Windows上的结果(区域设置为中文(简体, 中国))
    19. #sys.getdefaultencoding(): gbk
    20. #sys.getfilesystemencoding(): mbcs
    21. #locale.getdefaultlocale(): ("zh_CN", "cp936")
    22. #locale.getpreferredencoding(): cp936
    23. #"\xba\xba".decode("mbcs"): u"\u6c49"
    复制代码
    3.一些建议
    3.1. 使用字符编码声明,并且同一工程中的所有源代码文件使用相同的字符编码声明。
    这点是一定要做到的。
    3.2. 抛弃str,全部使用unicode。
    按引号前先按一下u最初做起来确实很不习惯而且经常会忘记再跑回去补,但如果这么做可以减少90%的编码问题。如果编码困扰不严重,可以不参考此条。
    3.3. 使用codecs.open()替代内置的open()。
    如果编码困扰不严重,可以不参考此条。
    3.4. 绝对需要避免使用的字符编码:MBCS/DBCS和UTF-16。
    这里说的MBCS不是指GBK什么的都不能用,而是不要使用Python里名为"MBCS"的编码,除非程序完全不移植。
    Python中编码"MBCS"与"DBCS"是同义词,指当前Windows环境中MBCS指代的编码。Linux的Python实现中没有这种编码,所以一旦移植到Linux一定会出现异常!另外,只要设定的Windows系统区域不同,MBCS指代的编码也是不一样的。分别设定不同的区域运行2.4小节中的代码的结果:
    1. #中文(简体, 中国)
    2. #sys.getdefaultencoding(): gbk
    3. #sys.getfilesystemencoding(): mbcs
    4. #locale.getdefaultlocale(): ("zh_CN", "cp936")
    5. #locale.getpreferredencoding(): cp936
    6. #"\xba\xba".decode("mbcs"): u"\u6c49"
    7. #英语(美国)
    8. #sys.getdefaultencoding(): UTF-8
    9. #sys.getfilesystemencoding(): mbcs
    10. #locale.getdefaultlocale(): ("zh_CN", "cp1252")
    11. #locale.getpreferredencoding(): cp1252
    12. #"\xba\xba".decode("mbcs"): u"\xba\xba"
    13. #德语(德国)
    14. #sys.getdefaultencoding(): gbk
    15. #sys.getfilesystemencoding(): mbcs
    16. #locale.getdefaultlocale(): ("zh_CN", "cp1252")
    17. #locale.getpreferredencoding(): cp1252
    18. #"\xba\xba".decode("mbcs"): u"\xba\xba"
    19. #日语(日本)
    20. #sys.getdefaultencoding(): gbk
    21. #sys.getfilesystemencoding(): mbcs
    22. #locale.getdefaultlocale(): ("zh_CN", "cp932")
    23. #locale.getpreferredencoding(): cp932
    24. #"\xba\xba".decode("mbcs"): u"\uff7a\uff7a"
    复制代码
    可见,更改区域后,使用mbcs解码得到了不正确的结果,所以,当我们需要使用"GBK"时,应该直接写"GBK",不要写成"MBCS"。
    UTF-16同理,虽然绝大多数操作系统中"UTF-16"是"UTF-16-LE"的同义词,但直接写"UTF-16-LE"只是多写3个字符而已,而万一某个操作系统中"UTF-16"变成了"UTF-16-BE"的同义词,就会有错误的结果。实际上,UTF-16用的相当少,但用到的时候还是需要注意。
    --END--
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-6-2 16:27 , Processed in 0.399180 second(s), 46 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表