【问题标题】:Why does json.dumps escape non-ascii characters with "\uxxxx" [duplicate]为什么json.dumps用“\uxxxx”转义非ascii字符[重复]
【发布时间】:2015-12-01 10:23:49
【问题描述】:

在 Python 2 中,函数 json.dumps() 将确保所有非 ascii 字符都转义为 \uxxxx

Python 2 Json

但这不是很令人困惑,因为\uxxxx 是一个 unicode 字符,应该在 unicode 字符串中使用。

json.dumps() 的输出是 str,它是 Python 2 中的字节字符串。因此它不应该将字符转义为 \xhh 吗?

>>> unicode_string = u"\u00f8"
>>> print unicode_string
ø
>>> print json.dumps(unicode_string)
"\u00f8"
>>> unicode_string.encode("utf8")
'\xc3\xb8'

【问题讨论】:

    标签: python json unicode python-2.x


    【解决方案1】:

    为什么json.dumps会用“\uxxxx”转义非ascii字符

    Python 2 可以将纯 ascii 字节字符串和 Unicode 字符串混合在一起。

    这可能是一个过早的优化。如果 Unicode 字符串在 Python 2 中主要包含 ASCII 范围内的字符,则它们可能需要比相应字节字符串多 2-4 倍的内存。

    此外,即使在今天,如果 print(unicode_string) 在打印到 Windows 控制台时包含非 ascii 字符,它也可能很容易失败,除非安装了 win-unicode-console Python 包之类的东西。如果使用 C/POSIX 语言环境(init.d 服务的默认设置,sshcron 在许多情况下),即使在 Unix 上它也可能会失败(这意味着 ascii 字符编码。有C.UTF-8,但它并不总是可用并且您必须明确配置它)。这或许可以解释为什么在某些情况下您可能需要ensure_ascii=True

    JSON 格式是为 Unicode 文本定义的,因此严格来说 json.dumps() 应该总是返回一个 Unicode 字符串,但如果所有字符都在 ASCII 范围内,它可能会返回一个字节串(xml.etree.ElementTree 有类似的“优化”)。在某些情况下,Python 2 允许将仅 ascii 的字节字符串视为 Unicode 字符串,这令人困惑(允许隐式转换)。 Python 3 更严格(禁止隐式转换)。

    可以使用纯 ASCII 字节字符串代替 Unicode 字符串(可能包含非 ASCII 字符)以节省内存和/或提高 Python 2 中的互操作性。

    要禁用该行为,请使用json.dumps(obj, ensure_ascii=False)


    避免将 Unicode 字符串与其在 Python 源代码中作为 Python 字符串文字的 表示形式 或其在文件中作为 JSON 文本的表示形式混淆,这一点很重要。

    JSON 格式允许转义任何字符,而不仅仅是 ASCII 范围之外的 Unicode 字符:

    >>> import json
    >>> json.loads(r'"\u0061"')
    u'a'
    >>> json.loads('"a"')
    u'a'
    

    不要将它与 Python 源代码中使用的 Python 字符串文字中的转义混淆。 u"\u00f8"单个 Unicode 字符,但输出中的 "\u00f8"八个 字符(在 Python 源代码 中,您可以将其正确为r'"\u00f8"' == '"\\u00f8"' == u'"\\u00f8"'(反斜杠在 Python literals 和 json 文本中都很特殊——双重转义可能发生)。JSON 中也没有 \x 转义:

    >>> json.loads(r'"\x61"') # invalid JSON
    Traceback (most recent call last):
    ...
    ValueError: Invalid \escape: line 1 column 2 (char 1)
    >>> r'"\x61"' # valid Python literal (6 characters)
    '"\\x61"'
    >>> '"\x61"'  # valid Python literal with escape sequence (3 characters)
    '"a"'
    

    json.dumps() 的输出是一个 str,它在 Python 2 中是一个字节字符串。因此它不应该将字符转义为 \xhh 吗?

    json.dumps(obj, ensure_ascii=True) 仅生成可打印的 ascii 字符,因此 print repr(json.dumps(u"\xf8")) 不会包含用于表示 (repr()) 不可打印字符(字节)的\xhh 转义。

    \u 转义即使对于纯 ascii 输入也是必要的:

    #!/usr/bin/env python2
    import json
    print json.dumps(map(unichr, range(128)))
    

    输出

    ["\u0000", "\u0001", "\u0002", "\u0003", "\u0004", "\u0005", "\u0006", "\u0007",
    "\b", "\t", "\n", "\u000b", "\f", "\r", "\u000e", "\u000f", "\u0010", "\u0011",
    "\u0012", "\u0013", "\u0014", "\u0015", "\u0016", "\u0017", "\u0018", "\u0019",
    "\u001a", "\u001b", "\u001c", "\u001d", "\u001e", "\u001f", " ", "!", "\"", "#",
    "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3",
    "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C",
    "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S",
    "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c",
    "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
    "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", "\u007f"]
    

    但这不是很混乱吗,因为 \uxxxx 是一个 unicode 字符,应该在 unicode 字符串中使用

    \uxxxx 是 6 个字符,在某些情况下可能被解释为单个字符,例如,在 Python 源代码中 u"\uxxxx" 是一个 Python 文字,它在内存中使用 single 创建一个 Unicode 字符串Unicode 字符。但是如果你在 json 文本中看到 \uxxxx;如果加载它,六个字符可能代表单个Unicode字符(json.loads())。

    此时你应该明白为什么len(json.loads('"\\\\"')) == 1了。

    【讨论】:

    • »此外,即使在今天,如果 print(unicode_string) 包含非 ascii 字符,它也可能很容易失败«可能是这里的重点。我会忽略内存使用,因为大多数人不会定期生成、解析或操作数十 Gibibytes 的 JSON。
    • @Joey:在其他条件相同的情况下,使用更少内存的算法通常更快。我已经说过,它可以被认为是一个过早的优化。将 json 打印到控制台对于调试很有用(并且调试,格式的人类可读性很重要),但在通常使用 json 的情况下使用 utf-8 并不难(程序之间的数据交换(可能在不同的计算机上))因此默认应该是Unicode
    【解决方案2】:

    "\u00f8" 中的\u 实际上并不是像\x 这样的转义序列。 \u 是文字 r'\u'。但是这样的字节串可以很容易地转换成Unicode。

    演示:

    s = "\u00f8"
    u = s.decode('unicode-escape')
    print repr(s), len(s), repr(u), len(u)
    
    s = "\u2122"
    u = s.decode('unicode-escape')
    print repr(s), len(s), repr(u), len(u)
    

    输出

    '\\u00f8' 6 u'\xf8' 1
    '\\u2122' 6 u'\u2122' 1
    

    正如 J.F.Sebastian 在 cmets 中提到的,在 Unicode 字符串 \u00f8 内是真正的转义码,即在 Python 3 字符串或 Python 2 u"\u00f8" 字符串中。也请注意他的其他言论!

    【讨论】:

    • 不要在 JSON 文本上使用 .decode('unicode-escape'),而是使用 json.loads(json_text)
    • @JFSebastian:我实际上并不是建议在 JSON 文本上使用 .decode('unicode-escape') ,只是说明 "\u00f8" 等不是转义序列,因为'\xf8''\n' 是。并展示如何在非 JSON 上下文中处理此类序列。另请注意,OP 正在谈论 JSON output;他们在处理 JSON input 的问题中没有提及任何内容。
    • \u00f8 Python unicode 文字和 JSON 文本中的转义序列。您不应该使用 .decode('unicode-escape') 来修复损坏的字节串文字,而应该使用 Unicode 文字 (u'')。如果 \uxxxx 以变量形式到达,则应使用 json 格式或 ast.literal_eval() 如果输入是 Python unicode 文字并且无法修复上游数据源。向初学者推荐.decode('unicode-escape') 具有误导性。
    • @J.F.Sebastian:很公平。这样更好吗?
    【解决方案3】:

    这正是重点。你得到一个字节字符串,而不是一个 Unicode 字符串。因此,Unicode 字符需要转义才能生存。 JSON 允许转义,因此提供了一种表示 Unicode 字符的安全方式。

    【讨论】:

    • 是的,我明白字节串的重点。但是在字节字符串中也允许 \u 转义吗?因为我只看到在字节字符串中使用了 \x 转义?
    • 这不是 Python 转义。这是一个 JSON 转义。
    • 如果您的 JSON 文件包含 \uXXXX 序列时您不喜欢它,您可以使用 print json.dumps(unicode_string, ensure_ascii=False),因此它返回 unicode 而不是字节字符串。我认为这些转义序列是处理破坏非 ASCII 字符的系统的遗留系统。
    • 这解释了为什么你必须转义非ascii字符如果结果是一个字节串并且你希望在不接受的环境中互操作非ASCII字节。它没有解释为什么你不能使用 utf-8 代替 ascii。它没有解释为什么json.dumps() 首先会返回一个字节串。 json.dumps() canshould 返回一个 Unicode 字符串。 My guess: a bytestring is used to save memory in Python 2
    猜你喜欢
    • 2013-06-16
    • 2015-02-20
    • 1970-01-01
    • 2014-06-12
    • 2014-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-13
    相关资源
    最近更新 更多