如何让Python解释器正确处理字符串操作中的非ASCII字符?

111
我有一个看起来像这样的字符串:
6 918 417 712

根据我的理解,Python中清晰明确地截取该字符串的方式是将该字符串存储在一个名为s的变量中,然后执行:

s.replace('Â ', '')

那应该就可以了。但当然,它会抱怨文件 blabla.py 中的非 ASCII 字符 '\xc2' 没有编码。

我从来没有完全理解如何在不同的编码之间切换。

这里是代码,它实际上与上面的代码完全相同,但现在它在上下文中。该文件以 UTF-8 格式保存在记事本中,并具有以下标头:

#!/usr/bin/python2.4
# -*- coding: utf-8 -*-

代码:

f = urllib.urlopen(url)

soup = BeautifulSoup(f)

s = soup.find('div', {'id':'main_count'})

#making a print 's' here goes well. it shows 6Â 918Â 417Â 712

s.replace('Â ','')

save_main_count(s)

它只能达到s.replace...


1
到目前为止尝试了所有4个答案。没有用。 仍然收到UnicodeDecodeError:'ascii'编解码器无法解码第1个位置的0xc2字节:序数不在范围内(128) - adergaard
你的 Unicode 字符串必须以 u 开头。 - SilentGhost
@SilentGhost:正如您所看到的,无法确定它是否为Unicode字符串。我得到了一个包含上述内容的字符串,但其中包含非ASCII字符串。这才是真正的问题。我猜测它是Unicode,因为它不在第一个128个字符内。 - adergaard
错误与输入字符串无关。是你代码中的一个字符串引发了这个错误! - SilentGhost
2
我敢打赌这就是为什么Python 3在字符串和字节序列之间严格区分的原因,只是为了避免这种混淆。 - Mark Ransom
显示剩余2条评论
12个回答

162

抛弃所有不能被解释为ASCII字符的字符:

def remove_non_ascii(s):
    return "".join(c for c in s if ord(c)<128)

请记住,这仅适用于UTF-8编码(因为多字节字符中的所有字节都将最高位设置为1)。


1
我得到了一个 TypeError: ord() 期望一个字符,但是发现长度为2的字符串。 - Ivelin
@Ivelin,这是因为“字符”没有被解释为正确的Unicode...请检查您的源字符串是否以u作为字面值前缀。 - fortran

87

Python 2使用ascii作为源文件的默认编码,这意味着你必须在文件顶部指定另一个编码才能在文字中使用非ASCII Unicode字符。而Python 3使用utf-8作为源文件的默认编码,因此这个问题就不那么重要了。

参见:http://docs.python.org/tutorial/interpreter.html#source-code-encoding

要启用 utf-8 源编码,应将以下内容放在前两行之一:

# -*- coding: utf-8 -*-

以上内容来自文档,但以下方式同样有效:

# coding: utf-8

其他需要注意的事项:

  • 源文件也必须在文本编辑器中使用正确的编码保存。

  • 在Python 2中,Unicode字面值前必须有u,例如:s.replace(u"Â ", u"")。但在Python 3中,只需使用引号。在Python 2中,您可以通过from __future__ import unicode_literals获得Python 3的行为,但请注意,这会影响整个当前模块。

  • 如果s不是Unicode字符串,则s.replace(u"Â ", u"")也会失败。

  • string.replace返回一个新字符串而不是直接编辑它,因此确保您也在使用返回值。


4
实际上,你只需要# coding: utf-8-*-不是用来装饰的,但你可能永远都不需要它。我认为它是为了适应旧的Shell而存在的。 - fmalina

41
>>> unicode_string = u"hello aåbäcö"
>>> unicode_string.encode("ascii", "ignore")
'hello abc'

5
我看到你获得了投票,但当我尝试时,它显示:不行。 UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 1: ordinal not in range(128)。可能是我的原始字符串不是Unicode吗? 无论如何,它需要处理。 - adergaard
3
好的,谢谢。我可以建议在结果上使用.decode()以获得原始编码吗? - AkiRoss
如果你遇到了UnicodeDecodeError: 'ascii'错误,那么在应用编码函数之前尝试将字符串转换为“UTF-8”格式。 - Sateesh

16

以下代码将使用问号替换所有非 ASCII 字符。

"".join([x if ord(x) < 128 else '?' for x in s])

出于好奇,我想知道,将其替换为问号是否有特定的原因? - Mohsin Aljiwala

6

使用正则表达式:

import re

strip_unicode = re.compile("([^-_a-zA-Z0-9!@#%&=,/'\";:~`\$\^\*\(\)\+\[\]\.\{\}\|\?\<\>\\]+|[^\s]+)")
print strip_unicode.sub('', u'6Â 918Â 417Â 712')

5

虽然回答有点晚了,但是原始字符串是以UTF-8编码的, '\xc2\xa0'代表不间断空格。只需将原始字符串解码为s.decode('utf-8') (\xa0在错误解码为Windows-1252或latin-1时会显示为空格)。

示例 (Python 3)

s = b'6\xc2\xa0918\xc2\xa0417\xc2\xa0712'
print(s.decode('latin-1')) # incorrectly decoded
u = s.decode('utf8') # correctly decoded
print(u)
print(u.replace('\N{NO-BREAK SPACE}','_'))
print(u.replace('\xa0','-')) # \xa0 is Unicode for NO-BREAK SPACE

输出

6 918 417 712
6 918 417 712
6_918_417_712
6-918-417-712

3
#!/usr/bin/env python
# -*- coding: utf-8 -*-

s = u"6Â 918Â 417Â 712"
s = s.replace(u"Â", "") 
print s

这将打印出6 918 417 712

不行。 UnicodeDecodeError: 'ascii'编解码器无法解码位置1处的0xc2字节:该序数不在128的范围内。可能是因为我的原始字符串不是Unicode格式吗? 无论如何,我可能做错了什么。 - adergaard
@adergaard,你在源文件顶部添加了 # -- coding: utf-8 -- 吗? - Nadia Alramli
是的,请再次查看此页面顶部,我已编辑了问题并放入了代码和标题注释。感谢您的帮助。 - adergaard
我认为你需要弄清楚如何从HTML或XML文档中以Unicode格式获取字符串。关于此的更多信息可以在这里找到:http://diveintopython.org/xml_processing/unicode.html - Isaiah

2

我知道这是一个旧的讨论串,但我觉得有必要提到"translate"方法,它总是一种很好的方法来替换所有大于128个字符代码(或者其他如果需要的话)。

用法:str.translate(table[, deletechars])

>>> trans_table = ''.join( [chr(i) for i in range(128)] + [' '] * 128 )

>>> 'Résultat'.translate(trans_table)
'R sultat'
>>> '6Â 918Â 417Â 712'.translate(trans_table)
'6  918  417  712'

Python 2.6开始,您还可以将表格设置为None,并使用deletechars删除不想要的字符,如标准文档中在http://docs.python.org/library/stdtypes.html中所示的示例。

对于Unicode字符串,翻译表不是一个256个字符的字符串,而是具有相关字符的ord()作为键的字典。但无论如何,从Unicode字符串获取正确的ASCII字符串非常简单,可以使用truppo上面提到的方法,即:unicode_string.encode("ascii","ignore")

总之,如果由于某种原因您绝对需要获取ASCII字符串(例如,当您使用raise Exception, ascii_message引发标准异常时),则可以使用以下函数:

trans_table = ''.join( [chr(i) for i in range(128)] + ['?'] * 128 )
def ascii(s):
    if isinstance(s, unicode):
        return s.encode('ascii', 'replace')
    else:
        return s.translate(trans_table)

翻译的好处在于,您实际上可以将带重音符号的字符转换为相关的非带重音的ASCII字符,而不是简单地删除它们或用“?”替换它们。这通常很有用,例如用于索引目的。


我得到了一个 TypeError 错误:字符映射必须返回整数、None 或 Unicode。 - Ivelin

1
s.replace(u'Â ', '')              # u before string is important

并将您的.py文件转换为Unicode编码。


1

这是一个不太正规的方法,但可能有效。

s2 = ""
for i in s:
    if ord(i) < 128:
        s2 += i

不断地向字符串追加通常不如构建列表然后连接高效。https://dev59.com/3XA75IYBdhLWcg3w4tN7 - user3064538

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