Python:为Unicode清理字符串?

14
我有一个字符串,我想让它适用于unicode()函数:

可能是重复:
Python UnicodeDecodeError - 我是否误解了编码?

>>> s = " foo “bar bar ” weasel"
>>> s.encode('utf-8', 'ignore')

Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    s.encode('utf-8', 'ignore')
UnicodeDecodeError: 'ascii' codec can't decode byte 0x93 in position 5: ordinal not in range(128)
>>> unicode(s)

Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    unicode(s)
UnicodeDecodeError: 'ascii' codec can't decode byte 0x93 in position 5: ordinal not in range(128)

我在这里大多数时间都是摸索。我需要做什么来从字符串中删除不安全的字符?

与此问题有些相关,但我无法从中解决我的问题。

这也失败了:

>>> s
' foo \x93bar bar \x94 weasel'
>>> s.decode('utf-8')

Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    s.decode('utf-8')
  File "C:\Python25\254\lib\encodings\utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0x93 in position 5: unexpected code byte

我在想为什么str具有encode函数,以及“编码”参数是否指定了结果的编码还是输入的编码。你到底想在这里做什么? - Thanatos
请查看这个相关问题的答案: “Python UnicodeDecodeError - 我是否误解了编码?” 链接:https://dev59.com/B3RC5IYBdhLWcg3wOeaB#370199 - tzot
对于那些正在寻找将Unicode特殊字符转换为(X)HTML的解决方案的人,请尝试使用u'my unicode str'.encode('ascii','xmlcharrefreplace') - 4Z4T4R
2个回答

41

很好的问题。编码问题是棘手的。让我们从"我有一个字符串。"开始。在Python 2中,字符串并不是真正的"字符串",它们是字节数组。那么你的字符串是从哪里来的,它使用的是什么编码?你的示例显示了弯引号,我甚至不确定你是如何做到的。我试图将其粘贴到Python解释器中,或者在Mac OS X上使用Option-[输入,但它没有出现。

然而,从你的第二个示例可以看出,你有一个十六进制为93的字符。这不能是UTF-8,因为在UTF-8中,任何高于127的字节都是多字节序列的一部分。因此,我猜它应该是Latin-1。问题是,x93不是Latin-1字符集中的字符。在Latin-1中存在这个被认为是非法的“无效”范围从x7f到x9f。然而,微软看到了这个未使用的范围,并决定在其中放置“弯引号”。这样他们创建了这个类似编码叫做"windows-1252",它类似于Latin-1但在那个无效范围内有一些东西。

所以,让我们假设它是windows-1252。现在怎么办?String.decode将字节转换为Unicode,因此这是你想要的。你的第二个示例走得很对,但它失败了,因为字符串不是UTF-8。尝试:

>>> uni = 'foo \x93bar bar\x94 weasel'.decode("windows-1252")
u'foo \u201cbar bar\u201d weasel'
>>> print uni
foo “bar bar” weasel
>>> type(uni)
<type 'unicode'>

没错,这是因为开头的花式引号是Unicode U+201C。现在你已经有了Unicode,你可以将其序列化为任何编码的字节(如果需要在网络上传递),或者如果它仅在Python中使用,则保留其为Unicode。如果你想转换为UTF-8,使用相反的函数,即字符串.encode。

>>> uni.encode("utf-8")
'foo \xe2\x80\x9cbar bar \xe2\x80\x9d weasel'

使用UTF-8编码花括号引号需要三个字节,而如果使用UTF-16则只需要两个字节。但是,无法使用ASCII或Latin-1进行编码,因为它们没有花括号引号。


1
+1,但您还应该提到此答案仅适用于Python 2.x。在3.x中,“str”类型被重命名为“bytes”,而“unicode”被重命名为“str”。虽然一开始可能会感到困惑,但这种更改使得这种情况不太可能发生。 - Daniel Pryden
让我们从“我有一个字符串”开始吧!哈哈。 - Nick Heiner
1
@Daniel 不要互相勾结,但我刚刚赞同了你的“赞同”解释。事实是:上述内容特指 Python 2.x。 - jpsimons
1
'\x80' - '\x9F' *被定义在Latin-1中。它们是C1控制字符,没有人使用。'\x93'是“设置传输状态”。 - dan04
当您正在Windows下运行时,有时可以使用“'mbcs'”而不是显式的代码页。 - Mark Ransom
显示剩余4条评论

5

编辑。看起来你的字符串被编码成这样,(左双引号)变成了\x93(右双引号)变成了\x94。有许多代码页具有这样的映射关系,CP1250是其中之一,因此你可以使用它:

s = s.decode('cp1250')

对于所有将映射为\x93的代码页,请参见此处(它们也都将映射为\x94,可以在此处验证)。

那个调用对我失败了(见上文) - Nick Heiner
@Rosarch 好的,现在我看到了原始字符串。我已经更新了答案(同时 @darkporter 也想出了同样的解决方案)。 - Bolo
代码页面上有一个不错的链接。看起来它们都是“Windows”的变体。如果你是“西方人”,我建议你只使用1252编码。 - jpsimons

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接