Python2 显示 unicode 的问题

2018-05-10 22:04:11 +08:00
 Jay54520

用户想要看的是 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.

>>> 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 处理字符串的问题

# 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"}

所以我的问题是,在 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/
4218 次点击
所在节点    Python
22 条回复
smallpython
2020-06-18 09:36:18 +08:00
你描述的这个案例打印不同是 print 函数的原因
你尝试把 print 的内容写到文件里, 显示的都是中文
smallpython
2020-06-18 09:39:16 +08:00
我猜测是 python2 的设计缺陷
否则不会在 python3 中重新定义了编码和 print 函数
你不用 print 而是用 logging 的话就避开了这个问题

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/453907

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX