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
Jay54520
V2EX  ›  Python

Python2 显示 unicode 的问题

  •  
  •   Jay54520 · 2018-05-10 22:04:11 +08:00 · 4192 次点击
    这是一个创建于 2393 天前的主题,其中的信息可能已经有所发展或是发生改变。

    用户想要看的是 u'中文' 而不是 u'\u4e2d\u6587',但是在 Python2 中有时并不能实现。

    转译

    转义字符是这样一个字符,标志着在一个字符序列中出现在它之后的后续几个字符采取一种替代解释[1]。

    >>> ["\u4e2d\u6587"] == ["中文"]
    True
    >>> '["\u4e2d\u6587"]' == '["中文"]'
    True
    
    # 取消转义后则不相等
    >>> r'["\u4e2d\u6587"]' == r'["中文"]'
    False
    >>> r'["\u4e2d\u6587"]'
    '["\\u4e2d\\u6587"]'
    >>> r'["中文"]'
    '["中文"]'
    

    由于各种语言的转义机制是不一样的,所以传递 '["\u4e2d\u6587"]' 到浏览器上,浏览器显示的是未转义的 '["\u4e2d\u6587"]'

    str()

    Python2 str is bytes.

    • unicode encode to bytes
    • bytes decode to unicode
    >>> b = u'中文'.encode('utf-8')
    >>> type(u'中文')
    <type 'unicode'>
    >>> type(b)
    <type 'str'>
    >>> b
    '\xe4\xb8\xad\xe6\x96\x87'
    >>> b.decode('utf-8') == u'中文'
    True
    

    对于 unicode,str() 相当于以默认 encoding 编码:

    # -*- coding: utf-8 -*-
    
    import sys
    try:
        str(u'中文')
    except UnicodeEncodeError:
        print(u'不能使用 {encoding} 编码非 {encoding} 字符'.format(encoding=sys.getdefaultencoding())) # 不能使用 ascii 编码非 ascii 字符
    
    reload(sys)
    sys.setdefaultencoding('UTF8')
    print(sys.getdefaultencoding()) # UTF8
    
    print(str(u'中文')) # 中文
    print(str(u'中文') == u'中文'.encode(sys.getdefaultencoding())) # True
    

    容器内的 unicode 显示

    容器 指一个类、数据结构或者一个抽象数据类型,对应的实例是其他对象的集合。在 Python 中,listdict 都是容器。

    在 Python 中,str(container) 对每个 item 调用 repr() 而不是 str() 以获取对应的字符串[2]。而在 Python2 中,repr() 返回一个对象的可打印字符串形式,但是会使用 \x\u 或者 \U 转译字符串中的非 ASCII 字符[3]。

    所以我们会看到这样的现象

    >>> print({u'\u4e2d\u6587': 1})
    {u'\u4e2d\u6587': 1}
    

    而在 Python3 中,由于默认编码是 UTF-8,所以 repr() 只会转译超出 UTF-8 范围的象形符号( glyphs ),所以在 Python3 中

    >>> print({u'\u4e2d\u6587': 1})
    {'中文': 1}
    

    print 做了什么

    Python 将 print() 中的参数转换为 bytes str,然后输出到 sys.stdout 上。

    目前不清楚如何转换的,只知道不是用 str() 转换:

    # -*- coding: utf-8 -*-
    
    print(u'中文') # 中文
    print(str(u'中文')) # UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
    

    处理

    显示时将 unicode 以 utf-8 编码为 bytes。

    由于 Python2 的默认编码是 ASCII 并且 str() 不支持 encoding 参数,所以不能使用 str()

    更改默认编码也不可取[4]。

    目前我找到的办法是使用 json.dumps(obj, ensure_ascii=False)

    If ensure_ascii is true (the default), all non-ASCII characters in the output are escaped with \uXXXX sequences, and the result is a str instance consisting of ASCII characters only. If ensure_ascii is false, some chunks written to fp may be unicode instances.

    >>> print(json.dumps([u'\u4e2d\u6587', ], ensure_ascii=False))
    ["中文"]
    >>> print(json.dumps([u'\u4e2d\u6587', ], ensure_ascii=True))
    ["\u4e2d\u6587"]
    

    使用 json 处理字符串的问题

    • 多次 dumps 引发会转译 \
    # coding: utf-8
    
    import json
    
    d = {
        json.dumps({u'中文': 'u 中文'}, ensure_ascii=False): 'value'
    }
    print(d)  # {u'{"\u4e2d\u6587": "\u4e2d\u6587"}': u'value'}
    print(json.dumps(d, ensure_ascii=False))  # {"{\"中文\": \"中文\"}": "value"}
    
    • 不知道 dumps 了几次

    所以我的问题是,在 Python2 中如何将容器转换为 unicode 以及正确显示 unicode。

    请不要说转 Python3,我就想找出一个在 Python2 中好的处理方法,并彻底弄清楚这个问题。而不是转了 Python3 之后,遇到编码问题又是一脸懵逼。

    参考

    1. https://zh.wikipedia.org/zh-hans/%E8%BD%AC%E4%B9%89%E5%AD%97%E7%AC%A6
    2. https://www.python.org/dev/peps/pep-3140/
    3. https://docs.python.org/3/library/functions.html#ascii
    4. https://anonbadger.wordpress.com/2015/06/16/why-sys-setdefaultencoding-will-break-code/
    22 条回复    2020-06-18 09:39:16 +08:00
    WordTian
        1
    WordTian  
       2018-05-10 22:11:11 +08:00 via Android
    把要输出的 unicode 字符串 encode 一下
    linux 下 encode 成 utf-8
    中文 windows 下 encode 成 gbk
    WordTian
        2
    WordTian  
       2018-05-10 22:15:59 +08:00 via Android
    要是网页的话,一般就是转成 utf-8。除非你自己把网页的编码指定成了其他格式
    Jay54520
        3
    Jay54520  
    OP
       2018-05-10 22:41:35 +08:00
    @WordTian

    unicode.encode('utf-8') 的意思是将 unicode 以 utf-8 格式编码为 bytes,那么为什么转换成 bytes 就能显示正常?我认为是其他应用(比如终端、浏览器)能将 bytes 以 utf-8 格式解码为 unicode。所以这个用来显示 unicode 应该没有问题。
    但你说的『中文在 windows 下 encode 成 gbk 』是不完善的。如果你仅仅处理中文还好,但我认为现在的话应用有很大几率遇到 gbk 范围外的字符,这样就会导致 encode error。所以 Windows 下也应该 unicode.encode('utf-8')。
    WordTian
        4
    WordTian  
       2018-05-10 22:49:36 +08:00 via Android
    可能我表述不当,那句话的意思是 在中文 windows 系统下,系统对中文的默认编解码方式是 gbk
    WordTian
        5
    WordTian  
       2018-05-10 22:55:54 +08:00 via Android
    @Jay54520 linux 系统的默认编解码方式是 utf-8

    浏览器支持多种解码方式,具体看你在响应头中设置的 charset 的值,一般来说常见的是 utf-8。当然也可以是别的编码,只要你 charset 的值设置的和网页中的编码一致即可
    est
        6
    est  
       2018-05-10 22:58:02 +08:00
    sys.setdefaultencoding('UTF8')

    这个根本就是国内流传的伪科学!
    Jay54520
        7
    Jay54520  
    OP
       2018-05-10 22:58:02 +08:00
    @WordTian
    在容器中 unicode.encode('utf-8') 后的显示还是有问题:

    ```python
    # coding: utf-8

    print({u'\u4e2d\u6587'.encode('utf-8'): 1}) # {'\xe4\xb8\xad\xe6\x96\x87': 1}
    ```

    因为就像上文所说,会对容器中的调用 repr():

    ```python
    print(u'\u4e2d\u6587'.encode('utf-8').__repr__()) # '\xe4\xb8\xad\xe6\x96\x87'
    ```

    所以问题还是出在对容器的字符串处理上面。
    laqow
        8
    laqow  
       2018-05-10 23:01:22 +08:00 via Android
    好像在哪看过 python 的 print 在 print 对象的时候很随便的,应该用对象自己定义的序列化方法,可能 print str(object)这种写法会好些。看看这个 https://blog.csdn.net/abccheng/article/details/61202648
    WordTian
        9
    WordTian  
       2018-05-10 23:03:57 +08:00 via Android
    你是在哪里显示的,是 windows 的 cmd ?还是 linux 的 shell 下?还是在 pycharm 下?
    你的 python 脚本用的什么编码格式保存的?
    这些你得先分清楚!
    Jay54520
        10
    Jay54520  
    OP
       2018-05-10 23:04:37 +08:00
    补充关于 print 的一些情况:

    ```python
    # coding: utf-8

    print(u'中文') # 中文
    print(u'中文'.__repr__()) # u'\u4e2d\u6587'
    print(u'中文'.__str__()) # UnicodeEncodeError
    ```

    ```python
    # coding: utf-8

    class A(object):

    def __str__(self):
    return 'str'

    def __repr__(self):
    return 'repr'


    print(A())
    print(A)
    ```
    Jay54520
        11
    Jay54520  
    OP
       2018-05-10 23:05:34 +08:00
    上面的一个示例漏了输出,这里补上:

    ```python
    # coding: utf-8

    class A(object):

    def __str__(self):
    return 'str'

    def __repr__(self):
    return 'repr'


    print(A()) # str
    print(A) # <class '__main__.A'>
    ```
    WordTian
        12
    WordTian  
       2018-05-11 00:03:04 +08:00 via Android
    @Jay54520 关于这句
    print({u'\u4e2d\u6587'.encode('utf-8'): 1})
    # {'\xe4\xb8\xad\xe6\x96\x87': 1}

    \xe4\xb8\xad \xe6\x96\x87,这是 中文 这两个字的 utf-8 编码,在内存中实际以 e4 b8 ad e6 96 87 这六个字节进行保存

    在具体输出时,python2 会调用系统默认的解码方式进行转换,当你单独输出 key 的值时,你就会看到它显示出的是中文
    laqow
        13
    laqow  
       2018-05-11 00:43:02 +08:00
    > class dict(dict):
    . def __str__(self):
    . return u'{' + u','.join([(u'"'+unicode(x[0])+u'":"'+unicode(x[1])+u'"') for x in self.items()]) + u'}'
    > a=dict({u"这":u"样",u"行":u"不"})
    > print(unicode(a))
    laqow
        14
    laqow  
       2018-05-11 00:51:12 +08:00
    要兼容 python3 就加个
    > def unicode(s):
    . return s
    Sylv
        15
    Sylv  
       2018-05-11 04:21:46 +08:00
    你的需求是在 Python 2 下直接 print 容器数据时内部的中文能直接显示出来, 但其实这只是个为了满足自己强迫症的“伪”需求,并不是一个编码问题。

    因为 {u'中文': u'中文'} 和 {u'\u4e2d\u6587': u'\u4e2d\u6587'} 是等价的,用 print 来调试代码时没有必要一定要显示容器内部元素的 str 值,反而显示内部元素的 repr 值更利于调试。

    例如 [u' ', u' '] 如果按你的要求 print 出来,内部的两个元素是看不出区别的,但实际上:
    >>> print([u' ', u' '])
    [u'\t', u' ']

    一个是制表符,另一个是空格。从调试代码的角度上来说,显然后者更有利。

    OK,你说是为了 print 出来给用户更好看。

    首先就不应该直接 print 容器数据给用户看,真正的“用户”是没有 print 容器数据的需求的,他们是看不懂 {u'语言': u'中文'} 是什么意思的,你应该要将其格式化为“用户”真正可以理解的字符串后输出,例如:“语言设置是中文”。否则你所说的“用户”其实指的是写代码的人,那么正如我上述所说,这是个“伪”需求。

    如果实在想要实现这个需求,你也不应该去折腾 print 和编码,因为 Python 的 print 就是这样设计的,这也并不是一个编码问题。你应该自己实现一个满足你需求的 print 方法,或者在 print 前将数据处理为可读形式后再 print,可以参考下下面这个链接里的方法:
    https://stackoverflow.com/a/45841899
    Jay54520
        16
    Jay54520  
    OP
       2018-05-11 09:34:17 +08:00
    @laqow @Sylv
    我认为二位的逻辑是在 Python2 中实现一个 new_str(obj) 函数,这个 new_str 函数的表现与 Python3 中的 str 一致。我在 stackoverflow 中也看到过相关的代码,但是不放心使用。因为代码要处理好嵌套的数据结构关系以及 Python 的所有的数据结构类型。我认为应该有测试好的库来做这个事情,比如 json 已经处理好了这样的关系,但是 json 也存在一些问题,见文章后半段。

    我认为如果 Python2 的 str 加上支持传入 encoding 以及 json 的 ensure_ascii 效果的参数就能解决这个问题。对于这样普遍的需求,我也认为会有一个完善的库或代码来处理。
    bravecarrot
        17
    bravecarrot  
       2018-05-11 13:37:41 +08:00
    py2 中 把 unicode encode 再 decode 转成 str 可破, 参数是"utf8 “
    Sylv
        18
    Sylv  
       2018-05-12 02:48:20 +08:00 via iPhone
    @Jay54520 到现在都没有这样一个完善的库也就说明了这样的需求并不是那么“普遍”。
    Jay54520
        19
    Jay54520  
    OP
       2018-05-12 21:12:23 +08:00
    @Sylv

    我说一下我现在能想起来的需求:

    1. Python2 中调试时查看容器中的内容。我现在是复制到 Python3 中再 print 出来,感觉很麻烦。
    2. 展示 MongoDB 聚合语句 -- 格式为 [dict(), dict()]
    mimvp
        20
    mimvp  
       2018-05-14 17:11:17 +08:00
    直接给出结果,看“ utf-8 ” 相关的行
    源码请见米扑博客: https://blog.mimvp.com/article/4441.html


    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    #
    # mimvp.com
    # 2015-11-09


    import urllib, urllib2
    import base64
    import socks, socket # 需要引入 socks.py 文件,请到米扑代理示例下载

    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')

    # 全局取消 ssl 证书验证,防止打开未验证的 https 网址抛出异常
    # urllib2.URLError:
    import ssl
    ssl._create_default_https_context = ssl._create_unverified_context
    smallpython
        21
    smallpython  
       2020-06-18 09:36:18 +08:00
    你描述的这个案例打印不同是 print 函数的原因
    你尝试把 print 的内容写到文件里, 显示的都是中文
    smallpython
        22
    smallpython  
       2020-06-18 09:39:16 +08:00
    我猜测是 python2 的设计缺陷
    否则不会在 python3 中重新定义了编码和 print 函数
    你不用 print 而是用 logging 的话就避开了这个问题
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1156 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 18:42 · PVG 02:42 · LAX 10:42 · JFK 13:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.