V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
NxnXgpuPSfsIT
V2EX  ›  Python

五分钟战胜 Python 字符编码

  •  
  •   NxnXgpuPSfsIT ·
    littlecodersh · 2016-06-23 10:44:08 +08:00 · 5902 次点击
    这是一个创建于 3060 天前的主题,其中的信息可能已经有所发展或是发生改变。

    五分钟战胜 Python 字符编码

    对于很多接触 Python 的人而言,字符的处理和语言整体的温顺可靠相比显得格外桀骜不驯难以驾驭。

    本文不谈复杂的理论,就经验教你字符处理八字真言:确定编码,同类交互。

    文章针对 Python 2.7 ,主要因为 3 对的编码已经有了很大的改善并且实际原理一样,更改一下操作命令即可。

    了解完本文,你可以轻松解决文字处理,特殊平台( Windows?)下的编码,爬虫编码等问题。

    阅读建议

    本文分为如下几个部分:

    • 原理
    • 具体操作
    • 建议的使用习惯
    • 疑难问题解答

    如果想要了解我给出的使用习惯,可以直接跳到建议的使用习惯。

    如果只想要解决相关问题可以直接跳到疑难问题解答。

    希望本文能够帮到你。

    原理

    为了理解方便,这里不谈理论只做类比,具体想要进一步了解各种编码的理论的搜狗一下好了。

    首先说一下我们为什么会碰到各式各样的编码问题:

    • 因为我们没有统一编码
    • 因为我们没有用对命令(传对数据)

    再说一下编码是什么, Python 的编码看似复杂,实际上可以看做只有两类编码: Unicode ,二进制

    • Unicode 相信都很熟悉:,就是\u0000这样的
    • 二进制编码也很简单,就是\x00\x00这样的,平常看到的utf-8,cp936都是二进制编码
    • 二进制编码是具象的,10001100原样就可以存储,而 Unicode 是抽象的,不能这样存
    #coding=utf8
    
    # Unicode 编码演示
    print('Unicode:')
    print(repr(u'Unicode 编码'))`
    
    # 二进制编码演示
    print(u'二进制编码:')
    print(repr('Unicode 编码'))`
    
    # 只是看个样子,代码不必去深究
    

    再说怎么做,就是只有同种编码之间才可以操作

    • 举个简单的类比
    就把一串数据比为烤鸭,我们作为人和鸭子不同种看待烤鸭的态度完全不一样。
    我们看到的是晚上的配菜,鸭子看到的是自己二舅。
    那么我在逛烤鸭店的时候用错编码就会报错。
    因为我在烤鸭店看到了满世界的二舅。
    
    • 这里说的同种就是我们熟悉的各种编码方式:utf-8,unicode,ucs-bom
    • 这也就是编码问题的核心,非常重要。

    最后说一下 Python 的环境

    • 本身代码是用 Ascii 解码的,文件里有 Ascii 无法解码的内容的话要告知 Python 怎么解码
    • 内部大量命令都是默认接受 Unicode
    # 告知的命令就是下面这一行,删掉就会报错
    #coding=utf8
    print(u'测试编码')
    

    具体操作

    拿到各种编码的内容自然是不用说,那么如果我们想要自己构造怎么做呢,看下面:

    #coding=utf8
    
    # 字符串前面加 u 会默认构造出 Unicode 的字符串
    unicodeString = u'Unicode 字符串'
    
    # 字符串前面什么都不加会构造出默认编码(首行限定了现在的 utf8 )的字符串
    utf8String = 'Utf-8 字符串'
    
    # 当然,没有首行,默认的编码是 Ascii
    

    那么他们之间怎么转换呢,同样很简单:

    # 接上一段程序
    
    # Unicode 转化为二进制编码中的一种: utf8
    unicodeString.encode('utf8')
    
    # 二进制编码根据自己的编码种类转化为 Unicode
    utf8String.decode('utf8')
    
    # 如果二进制编码中混进了奇怪的东西可以根据需求用特殊的 decode 策略
    print(repr('u8 字\x00 符串'.decode('utf8', 'replace')))
    

    那么怎么样会出现问题呢:

    # 接上一段程序
    
    # 如果我们把他们转化成同样的编码方式就可以操作(例如相加)
    print(repr(unicodeString + utf8String.decode('utf8')))
    print(repr(unicodeString.encode('utf8') + utf8String))
    
    # 但如果不转化,当然就会出现满世界的烤鸭二舅啦
    unicodeString + utf8String
    
    # 所以另一方面也发现,编码转换是需要我们告诉程序怎么做的
    # 所有`decode`操作都会生成 Unicode 编码,这是为了方便我之前说的大量接受 Unicode 的内部命令
    

    所以我们需要确定程序使用的编码,这是我们需要告诉程序的东西

    • 一方面在操作字符串的时候确定是同种编码
    • 另一方面在使用非自己写的命令时,一般使用 Unicode ,或者使用接收二进制编码的命令
    #coding=utf8
    # 这里拿写入文件举例
    
    # 一般使用 Unicode
    with open('Unicode.txt', 'w') as f: f.write(u'Unicode 测试')
    
    # 或者使用接收二进制编码的命令
    with open('Utf8.txt', 'wb') as f: f.write('Utf8 测试')
    
    # 你可以反过来做个测试,自然会报错
    # 二进制的命令方便了在不知道怎么解码的情况下也能进行操作(写入文件)
    

    我建议的使用习惯

    相信到这里我已经把我对于编码的理解讲完了。

    我们为什么会碰到各式各样的编码问题:

    • 因为我们没有统一编码
    • 因为我们没有用对命令(传对数据)

    所以这里再重申一下八字真言:确定编码,同类交互

    • 碰到问题,问一下自己,我现在是哪种编码
    • 同一种编码才能交互,那我应该是哪种编码

    这里给出我的使用习惯:

    • 确定一种内部编码
    • 内部编码的选择优先级如下:程序必须使用的编码、第三方包使用的编码、你喜欢的编码、 Unicode
    • 在输出时再更改到特定的编码

    记得在开始整个程序之前确定内部的编码,否则编码一团糟会产生很多不必要的 bug 。

    不要迷信内部 Unicode ,例如 Evernote 开发就应该根据第三方包使用的 Utf8 确定内部编码。

    疑难问题解答

    编码识别

    说了要确定编码,那么拿到一串二进制要怎么确定编码呢?

    最简单的方法是chardet:(需要安装)

    python -m pip install chardet
    

    使用非常简单:

    #coding=utf8
    
    from chardet import detect
    print(detect('这是一串 utf8 的测试字符'))
    
    # 结果:`{'confidence': 0.99, 'encoding': 'utf-8'}`
    

    另外例如抓取网站,那么头文件中很有可能有提示如何解码,记得不要忘记了。

    编码转换

    很可能因为字符串中参杂了奇怪的东西,导致即使编码种类正确,依旧无法解码。

    我知道我之前讲过了,但可能有人直接跳疑难问题解答嘛。

    这里可以使用decode的第二个参数:

    #coding=utf8
    
    # 字符串中混进了\x00
    rubbishUtf8String = 'Utf-8 字\x00 符串'
    
    print(repr(rubbishUtf8String.decode('utf8', 'replace')))
    
    print(repr(rubbishUtf8String.decode('utf8', 'ignore')))
    

    特殊平台下编码

    很多人都说 Windows 是个坑,即使在 Python 3 下面也一样。

    因为中文文件名出来都是乱码。

    这里使用一个取巧的方法:平台编码再特殊,起码命令行读取和创建一个文件夹不会出乱码吧。

    import sys, os
    
    for folder in os.walk('.').next()[1]:
        print(folder.decode(sys.stdin.encoding))
    

    同样的输入输出也可以这样做优化:

    import sys
    
    def sys_print(msg):
        print(msg.encode(sys.stdin.encoding))
    
    def sys_input(msg):
        return raw_input(msg.encode(sys.stdin.encoding)).decode(sys.stdin.encoding)
    

    文件写入

    如果抓下来一个内容不知道怎么解码,但还是想要写入文件怎么办

    写入文件的时候制定用二进制命令即可:

    #coding=utf8
    import urllib
    
    with open('Utf8.txt', 'wb') as f: f.write('Utf8 测试')
    
    # 比如抓了个网页,不知道编码也可以写入文件进行一系列操作
    
    content = urllib.urlopen('http://www.baidu.com').read()
    with open('baidu.txt', 'wb') as f: f.write(content)
    

    裸 Unicode 字符

    Unicode 存成六个 Ascii 字符怎么办?其实也可以decode

    #coding=utf8
    # 这是普通的 Unicode
    s = u'测'
    for i in s: print(i)
    print(repr(s))
    
    # 这是裸 Unicode ,实际存成了六个 Ascii
    s = repr(s)[2:-1]
    for i in s: print(i)
    print(repr(s))
    
    # 转化其实也很简单
    s = s.decode('unicode-escape')
    for i in s: print(i)
    print(repr(s))
    

    结束语

    希望读完这篇文章能对你有帮助,有什么不足之处万望指正(鞠躬)。

    有什么想法或者想要关注我的更新,欢迎来GithubStar或者Fork我的项目。

    160623

    LittleCoder

    EOF

    23 条回复    2016-06-24 23:05:24 +08:00
    louzhumuyou
        1
    louzhumuyou  
       2016-06-23 10:54:22 +08:00
    赞一个,虽然看的懵懵懂懂的
    magicdawn
        2
    magicdawn  
       2016-06-23 10:56:16 +08:00
    pyenv install 3.5.1 # 哈哈
    est
        3
    est  
       2016-06-23 11:05:04 +08:00   ❤️ 3
    一本正经的胡说八道
    NxnXgpuPSfsIT
        4
    NxnXgpuPSfsIT  
    OP
       2016-06-23 11:07:12 +08:00
    @est 怎么说?
    ryd994
        5
    ryd994  
       2016-06-23 11:07:54 +08:00 via Android   ❤️ 1
    没别的事就全用 Unicode ………
    NxnXgpuPSfsIT
        6
    NxnXgpuPSfsIT  
    OP
       2016-06-23 11:08:11 +08:00
    @magicdawn 很多时候碰到编码问题的第一建议都是,去用 3 ,哈哈
    NxnXgpuPSfsIT
        7
    NxnXgpuPSfsIT  
    OP
       2016-06-23 11:11:36 +08:00
    @ryd994 对的,非常赞同。不过出于严谨考虑,用第三方包之前还是可以考虑一下第三方包的编码,我写命令行印象笔记的时候中期才改 Utf8 编码方式,非常痛苦。
    practicer
        8
    practicer  
       2016-06-23 11:16:43 +08:00
    在自学 python 五个月后突然明白了编码问题,


    抄代码抄得竟然想通了 =。=
    NxnXgpuPSfsIT
        9
    NxnXgpuPSfsIT  
    OP
       2016-06-23 11:19:40 +08:00
    @practicer 我也这样,也是悲伤
    xiandao7997
        10
    xiandao7997  
       2016-06-23 12:23:19 +08:00 via Android
    @practicer 想通了那有分享吗?
    practicer
        11
    practicer  
       2016-06-23 12:41:31 +08:00
    @xiandao7997 没有很系统的了解,只有当遇到具体编码 error 时知道怎么调,我用 py2 入的门。

    例如当返回的字符有编码错误,我先 type(x)查一下该结果是 string 还是 Unicode ,如果是 U ,直接 encode('GBK')或 encode('UTF-8'),此时如果仍然出错,看具体 error ,大多数情况那是因为结果中还存在连'GBK'或'UTF-8'编码都不认识的字符,这个时候用 encode('GBK', 'ignore'),即可过滤那部分不认识的字符, error 就消失了。
    当 type(x)为 string 时,那么需要先解码为 Unicode 。选 decode('GBK')还是 decode('UTF-8'),或其他编码?需要先了解传入解释器的源字符串的编码格式,如果字符串来自于所爬取的是 utf-8 网页,则选 decode('UTF-8'),如果是 GBK ,则 decode('GBK')。 decode 后字符串转成了 unicode ,面对 Unicode ,和前面处理 Unicode 相同,根据自己的需求 encode 即可。
    imn1
        12
    imn1  
       2016-06-23 14:01:15 +08:00
    windows 还需要讲 cmd 上的输出, py3 输入不难解决
    此外,不论什么语言,数据库的编码也是要重点讲的,很多人存入再读出后就傻眼了
    imn1
        13
    imn1  
       2016-06-23 14:02:16 +08:00
    另外,这文 5 分钟读不完
    mozartgho
        14
    mozartgho  
       2016-06-23 14:31:17 +08:00
    写的很不错,感谢分享!
    killerv
        16
    killerv  
       2016-06-23 14:32:48 +08:00
    我是全用 unicode
    nankingpython
        17
    nankingpython  
       2016-06-24 03:57:54 +08:00
    https://www.v2ex.com/t/287940#reply0 看了你的貼字。为什么这样不行呢
    dofine
        18
    dofine  
       2016-06-24 12:36:34 +08:00 via iPhone
    本来感觉自己挺懂的…看完怎么更蒙蔽了……
    shamashii
        19
    shamashii  
       2016-06-24 13:10:03 +08:00
    然而一些 unicode-only 字符 cmd 上 print 还是无力,比如ß、🚲。另外还和字体有关, cmd 下即使调成了 65001 也就支持两个 console 字体。幸好写入文件什么的没问题,虽然 print 乱码
    sxul07
        20
    sxul07  
       2016-06-24 13:19:36 +08:00
    [在烤鸭店看到了满世界的二舅]
    这个比喻笑 cry
    dossec
        21
    dossec  
       2016-06-24 18:39:46 +08:00
    这个不错,学习了。
    NxnXgpuPSfsIT
        22
    NxnXgpuPSfsIT  
    OP
       2016-06-24 19:13:15 +08:00
    @nankingpython 已回复
    nankingpython
        23
    nankingpython  
       2016-06-24 23:05:24 +08:00
    @NxnXgpuPSfsIT 太感谢了,解决了,虽然打不出中文好歹不报错了。打出全是 /xxx 之类的,我研究下
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5379 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 08:59 · PVG 16:59 · LAX 00:59 · JFK 03:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.