为什么这个转换为utf8不工作?

16 投票
5 回答
15588 浏览
提问于 2025-04-17 00:12

我有一个子进程命令,它输出了一些字符,比如'\xf1'。我尝试用utf8来解码,但出现了错误。

s = '\xf1'
s.decode('utf-8')

上面的代码会抛出:

UnicodeDecodeError: 'utf8' codec can't decode byte 0xf1 in position 0: unexpected end of data

当我使用'latin-1'时,它可以正常工作,但utf8不应该也能工作吗?我理解的是,latin1是utf8的一个子集。

我是不是漏掉了什么?

编辑:

print s # ñ
repr(s) # returns "'\\xa9'"

5 个回答

1

这是一个多字节序列中UTF-8编码的第一个字节,所以单独使用它是不合法的。

实际上,它是一个4字节序列的第一个字节。

Bits Last code point Byte 1   Byte 2   Byte 3   Byte 4   Byte 5   Byte 6
21   U+1FFFFF        11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

想了解更多信息,可以查看 这里

4

UTF-8并不是Latin-1的一个子集。UTF-8用相同的单字节来编码ASCII字符。对于其他所有字符,它都是用多个字节来表示的。

简单来说,\xf1在UTF-8中是无效的,正如Python所提示的那样。“意外的输入结束”意味着这个字节是一个多字节序列的开始,但后面的字节没有提供。

我建议你去了解一下UTF-8

9

你把Unicode和UTF-8搞混了。拉丁-1是Unicode的一个子集,但它不是UTF-8的子集。千万不要去想单个的代码单位。 只需要使用代码点。不要去考虑UTF-8,想想Unicode就可以了。这就是你感到困惑的地方。

示例程序的源代码

在Python中使用Unicode非常简单。特别是在Python 3和宽字符构建下,这是我使用Python的唯一方式,不过如果你小心使用UTF-8,仍然可以在窄字符构建下使用旧版的Python 2。

要做到这一点,始终确保你的源代码编码和输出编码都设置为UTF-8。现在不要再考虑任何UTF的东西,只用UTF-8字面量、逻辑代码点数字或符号字符名称在你的Python程序中。

以下是带行号的源代码:

% cat -n /tmp/py
     1  #!/usr/bin/env python3.2
     2  # -*- coding: UTF-8 -*-
     3  
     4  from __future__ import unicode_literals
     5  from __future__ import print_function
     6  
     7  import sys
     8  import os
     9  import re
    10  
    11  if not (("PYTHONIOENCODING" in os.environ)
    12              and
    13          re.search("^utf-?8$", os.environ["PYTHONIOENCODING"], re.I)):
    14      sys.stderr.write(sys.argv[0] + ": Please set your PYTHONIOENCODING envariable to utf8\n")
    15      sys.exit(1)
    16  
    17  print('1a: el ni\xF1o')
    18  print('2a: el nin\u0303o')
    19  
    20  print('1a: el niño')
    21  print('2b: el niño')
    22  
    23  print('1c: el ni\N{LATIN SMALL LETTER N WITH TILDE}o')
    24  print('2c: el nin\N{COMBINING TILDE}o')

这里是打印函数,包含它们的非ASCII字符,使用uniquoted\x{⋯}表示法:

% grep -n ^print /tmp/py | uniquote -x
17:print('1a: el ni\xF1o')
18:print('2a: el nin\u0303o')
20:print('1b: el ni\x{F1}o')
21:print('2b: el nin\x{303}o')
23:print('1c: el ni\N{LATIN SMALL LETTER N WITH TILDE}o')
24:print('2c: el nin\N{COMBINING TILDE}o')

示例程序的运行结果

这是该程序的一个示例运行,展示了三种不同的方式(a、b和c):第一组是你源代码中的字面量(这会受到StackOverflow的NFC转换影响,所以不能完全信任!!!),第二组和第三组分别是数字Unicode代码点符号Unicode字符名称,同样使用uniquoted,这样你可以看到真实的内容:

% python /tmp/py
1a: el niño
2a: el niño
1b: el niño
2b: el niño
1c: el niño
2c: el niño

% python /tmp/py | uniquote -x
1a: el ni\x{F1}o
2a: el nin\x{303}o
1b: el ni\x{F1}o
2b: el nin\x{303}o
1c: el ni\x{F1}o
2c: el nin\x{303}o

% python /tmp/py | uniquote -v
1a: el ni\N{LATIN SMALL LETTER N WITH TILDE}o
2a: el nin\N{COMBINING TILDE}o
1b: el ni\N{LATIN SMALL LETTER N WITH TILDE}o
2b: el nin\N{COMBINING TILDE}o
1c: el ni\N{LATIN SMALL LETTER N WITH TILDE}o
2c: el nin\N{COMBINING TILDE}o

我真的不喜欢看二进制,但这就是它作为二进制字节的样子:

% python /tmp/py | uniquote -b
1a: el ni\xC3\xB1o
2a: el nin\xCC\x83o
1b: el ni\xC3\xB1o
2b: el nin\xCC\x83o
1c: el ni\xC3\xB1o
2c: el nin\xCC\x83o

故事的道理

即使你使用UTF-8源代码,你也应该只考虑和使用逻辑Unicode代码点数字(或符号命名字符),而不是底层的8位代码单位,这些单位构成了UTF-8(或者UTF-16)的串行表示。需要代码单位而不是代码点的情况非常少,这只会让你感到困惑。

如果你使用Python 3的宽字符构建,你会得到更可靠的行为,而不是其他选择,但这与UTF-32有关,而不是UTF-8。只要顺其自然,UTF-32和UTF-8都很容易使用。

撰写回答