Python中将一些字符串(utf-8或其他格式)转换为简单ASCII字符串的绝对可靠方法是什么?

5

在我的Python脚本中,我从一个我没有编写的函数中获取一些字符串。它的编码方式各不相同。我需要将其转换为ASCII格式。有什么绝对可靠的方法吗?我不介意用空格或其他字符替换非ASCII字符...

5个回答

10
如果您想要一个明确表示您所得到的内容,而不会丢失任何信息的ASCII字符串,答案很简单:
不要使用编码/解码,使用repr()函数(Python 2.X)或ascii()函数(Python 3.x)。

5
您说“它的编码各不相同”。我猜您指的是Python 2.x中的“字符串”,它实际上是一个字节序列。
回答第一部分:如果您不知道已编码字符串的编码方式,那么没有任何办法对其进行有意义的操作。如果您知道编码方式,第一步是将str转换为unicode类型:
encoded_string = i_have_no_control()
the_encoding = 'utf-8' # for the sake of example
text = unicode(encoded_string, the_encoding)

如果你愿意,你可以将Unicode对象重新编码为ASCII。

ascii_garbage = text.encode('ascii', 'replace')

* 有启发式方法来猜测编码,但它们速度较慢且不可靠。这里是一个在Python中的优秀尝试:chardet


“不,根本没有办法做任何有意义的事情。”--今天使用的几乎所有字符集都从ASCII继承其小写字符。在这种情况下,有一些有意义的事情可以做:丢弃所有非ASCII字符。这就是提问者想要的。 UTF-16和UTF-32是例外情况,永远不会与任何其他字符集混淆,因此我认为可以安全地忽略它们。 - intgr
你似乎认为世界上唯一的字符编码是由Unicode定义的,但事实并非如此。还有许多常用的字符编码,例如shift-jis、windows-1252等。而且,“转换为ASCII”通常意味着“规范化”字符,例如将ä转换为a,这显然不能通过假设您的编码每个字符占一个字节,并掩盖非ASCII字节来完成,正如您所建议的那样! - Jonathan Feinberg
Shift-JIS和Windows-1252都继承了ASCII的低位ASCII代码点。因此,在常见情况下,剥离所有高位设置的字符(这就是我的答案所做的)是有效的。这并不理想,但在许多情况下足够。如果您根本不知道编码,则显然无法对其进行规范化。至于自动检测,ISO-8859-*系列中的一些字符集具有如此多的重叠和歧义,以至于它们基本上是不可能区分的。 - intgr

4

我会尝试对字符串进行规范化,然后再进行编码。比如:

import unicodedata
s = u"éèêàùçÇ"
print unicodedata.normalize('NFKD',s).encode('ascii','ignore')

只有在输入为Unicode时才有效。 因此,您必须知道函数输出的编码方式并对其进行解码。如果不知道,可以使用编码检测启发式算法,但在短字符串上,这些算法不可靠。

当然,你可能会有好运,函数的输出依赖于各种未知编码,但以ASCII为代码基础,因此它们将为0到127的字节分配相同的值(例如UTF-8)。

在这种情况下,您可以使用OrderedSets过滤掉不需要的字符:

import string.printable # asccii chars
print "".join(OrderedSet(string.printable) & OrderedSet(s))

或者如果您想要空白符:
print("".join(((char if char in  string.printable else " ") for char in s )))

"translate"可以帮助您做同样的事情。唯一知道你是否幸运的方法是尝试一下...有时候,一个幸运的大日子是任何开发者所需要的 :-)

2
“防傻”是指该功能即使接收到最奇怪、最不可能的输入,也不会失败——也就是说,您可以向该函数提供任意二进制数据,它都永远不会失败。这就是“防傻”的含义。
然后,该功能应尽力将其转换为目标编码。如果必须丢弃所有不理解的内容,那么这是完全可以接受的,实际上也是最理想的结果。为什么要试图拯救所有垃圾呢?只需丢弃垃圾。告诉用户,他不仅仅是使用 Microsoft 产品的蠢货,而且是使用非标准 Microsoft 产品的非标准蠢货……或者试图发送二进制数据的蠢货!
我刚好也有同样的需求(尽管我的需求是在 PHP 中),而且我还有一些至少和我一样蠢的用户,有时甚至更蠢;但是,他们绝对更友善,毫无疑问更有耐心。
到目前为止,我发现最好的底线方法是(在 PHP 5.3 中): $fixed_string = iconv('ISO-8859-1', 'UTF-8//IGNORE//TRANSLATE', $in_string);
这个函数尝试翻译它能够翻译的任何内容,然后简单地丢弃所有垃圾,从而产生一个合法的 UTF-8 字符串输出。我也无法打破它或使其失败或拒绝任何传入的文本或数据,即使通过向它提供大量的二进制垃圾数据。
找到 iconv() 并让它工作很容易;令人发狂和浪费的是阅读所有的废话和扭曲的愚蠢,这些愚蠢似乎在处理这个编码问题时都会出现。那些古老的编程“抨击和烧毁白痴”的可贵(和值得尊重)精神去哪了?让我们回归基础。使用 iconv() 丢弃他们的垃圾,当告诉他们你丢弃了他们的垃圾时不要害羞——简而言之,不要忘记抨击那些给你提供垃圾的蠢货。你可以告诉他们我告诉了你这些。

1

如果你只想保留ASCII兼容字符并丢弃其余字符,那么在大多数编码中,这归结为删除所有具有高位设置的字符--即值超过127的字符。这是有效的,因为几乎所有字符集都是7位ASCII的扩展。

如果它是一个普通字符串(即不是unicode),则需要在任意字符集(例如iso-8859-1,因为它接受任何字节值)中进行解码,然后使用ignorereplace选项对错误进行ASCII编码:

>>> orig = '1ä2äö3öü4ü'
>>> orig.decode('iso-8859-1').encode('ascii', 'ignore')
'1234'
>>> orig.decode('iso-8859-1').encode('ascii', 'replace')
'1??2????3????4??'

解码步骤是必要的,因为您需要一个Unicode字符串才能使用编码。如果您已经有了一个Unicode字符串,那么就更简单了:

>>> orig = u'1ä2äö3öü4ü'
>>> orig.encode('ascii', 'ignore')
'1234'
>>> orig.encode('ascii', 'replace')
'1??2????3????4??'

直接将字符串转换为 ASCII 码(作为 Unicode 对象)也是可能的:'1ä2äö3öü4ü'.decode("ascii", "ignore")。在我看来,仅仅因为你使用了简化字符集并不意味着 Unicode 类型对于文本字符串是一个不好的选择。 - u0b34a0f6ae
如果您的默认编码不是iso-8859-1,则在尝试将源字符串解码为iso-8859-1时,您的第一行代码将会出错。 - Jonathan Feinberg
@Jonathan Feinberg:从 iso-8859-1 解码永远不会失败,因为任何字符序列都有定义的含义并且在 ISO-8559-1 中是合法的。默认编码与此有什么关系?我明确地在所有地方指定编码。 - intgr
@kaizer.se:使用 'ignore' 可以正常工作,但是当你使用 'replace' 时,它会给你一个带有 Unicode 字符串的结果:u'1\ufffd\ufffd2\ufffd\ufffd\ufffd\ufffd3\ufffd\ufffd\ufffd\ufffd4\ufffd\ufffd' - intgr
抛弃非ASCII字符通常就像把婴儿和洗澡水一起扔掉。例如,在典型的中文网站(charset = gb2312,但不要相信它,应该读作charset = gb2312的某个超集,尝试使用gbk编解码器),ASCII兼容字符大多是HTML语法;内容大多是中文,并且会被所有转换破坏。同样的情况也适用于俄语。请注意,koi8_r(但不是cp1251)有一个内置技巧:ucity = u"\u041c\u043e\u0441\u043a\u0432\u0430"; ''.join(chr(ord(c) & 0x7f) for c in ucity.encode('koi8_r')) 会产生 'mOSKWA' - John Machin

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