如何去除非ASCII字符但保留句点和空格?

133

我正在处理一个 .txt 文件。我想要从文件中获得文本字符串,但是不包含非 ASCII 字符。但是,我想要保留空格和句号。目前,我的代码把它们也去掉了。以下是代码:

def onlyascii(char):
    if ord(char) < 48 or ord(char) > 127: return ''
    else: return char

def get_my_string(file_path):
    f=open(file_path,'r')
    data=f.read()
    f.close()
    filtered_data=filter(onlyascii, data)
    filtered_data = filtered_data.lower()
    return filtered_data

我该如何修改onlyascii()函数以保留空格和句号?我想这并不复杂,但我无法想出如何实现。


感谢您的澄清,John。我明白空格和句号都是ASCII字符。然而,在尝试仅删除非ASCII字符时,我无意中将它们都删除了。我看到我的问题可能会让人产生误解。 - user1120342
@PoliticalEconomist:你的问题仍然没有具体说明。请看我的回答。 - John Machin
8个回答

226

您可以使用string.printable来过滤所有非可打印字符的字符串,例如:

>>> s = "some\x00string. with\x15 funny characters"
>>> import string
>>> printable = set(string.printable)
>>> filter(lambda x: x in printable, s)
'somestring. with funny characters'

在我的机器上,string.printable 包含:

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c

编辑:在Python 3上,filter将返回一个可迭代对象。正确的方法是获得一个字符串返回:

''.join(filter(lambda x: x in printable, s))

3
那些在48以下序数的可打印字符是怎么回事? - joaquin
42
使用 filter 的唯一问题在于它返回一个可迭代对象。如果你需要一个字符串(就像我在列表推导时所需的那样),那么可以这样做:''.join(filter(lambda x: x in string.printable, s)) - cjbarth
5
@cjbarth的评论是针对Python 3特定的,但非常有用。谢谢! - undershock
8
为什么不使用正则表达式:re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string)。请参考此帖子https://dev59.com/xWIj5IYBdhLWcg3wmmNE#20079244。 - Noam Manos
1
@NoamManos,这对我来说比join...filter...lambda的解决方案快4-5倍,谢谢。 - artfulrobot
显示剩余18条评论

117

通过使用encode()或decode()函数,可以轻松将编解码方式更改为不同的编码方式。在您的情况下,您想要转换为ASCII并忽略所有不支持的符号。例如,瑞典字母å不是ASCII字符:

    >>>s = u'Good bye in Swedish is Hej d\xe5'
    >>>s = s.encode('ascii',errors='ignore')
    >>>print s
    Good bye in Swedish is Hej d

编辑:

Python3: str -> bytes -> str

>>>"Hej då".encode("ascii", errors="ignore").decode()
'hej d'

Python2: unicode -> str -> unicode

Python2:unicode -> str -> unicode
>>> u"hej då".encode("ascii", errors="ignore").decode()
u'hej d'

Python2: str -> unicode -> str (反向解码和编码)

>>> "hej d\xe5".decode("ascii", errors="ignore").encode()
'hej d'

18
我遇到了一个UnicodeDecodeError错误:“ascii”编解码器无法解码第27个位置的0xc2字节。 - Xodarap777
2
当我通过复制粘贴将实际的Unicode字符放入字符串中时,我遇到了该错误。当您将字符串指定为u'thestring'时,编码会正常工作。 - Ben Liyanage
2
只能在Py3上运行,但很优雅。 - gaborous
8
对于那些遇到与@Xodarap777相同错误的人:你应该首先对字符串进行.decode()操作,然后再进行编码.encode()。例如s.decode('utf-8').encode('ascii', errors='ignore') - Spc_555

42

根据 @artfulrobot 的说法,这应该比 filter 和 lambda 更快:

import re
re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string) 

在此处查看更多示例 用单个空格替换非 ASCII 字符


1
该解决方案回答了OP所提出的问题,但请注意它不会删除ASCII中包含的非可打印字符,我认为这就是OP想要询问的内容。 - Danilo Souza Morães

8
您可以使用以下代码移除非英文字母:
import re
str = "123456790 ABC#%? .(朱惠英)"
result = re.sub(r'[^\x00-\x7f]',r'', str)
print(result)

这将返回

123456790 ABC#%? .()


你能解释一下你使用的正则表达式吗?r'[^\x00-\x7f]' - Reihan_amn
不应将变量命名为str,因为那是Python的内置类型。 - undefined

6
你的问题有歧义;前两个句子一起看,似乎你认为空格和“period”不是ASCII字符。这是不正确的。所有ord(char) <= 127的字符都是ASCII字符。例如,你的函数排除了这些字符!"#$%&\'()*+,-./,但包括其他几个字符,例如[]{}。
请退后一步,思考一下,并编辑你的问题,告诉我们你想做什么,不要提及ASCII这个词,以及为什么你认为ord(char) >= 128的字符可以忽略。还有:Python的哪个版本?你的输入数据的编码是什么?
请注意,你的代码将整个输入文件读取为一个字符串,并且你对另一个答案的评论(“great solution”)意味着你不关心数据中的换行符。如果你的文件包含两行像这样的内容:
this is line 1
this is line 2

结果将会是'这是第一行,这是第二行'... 这就是你真正想要的吗?

更好的解决方案应该包括:

  1. a better name for the filter function than onlyascii
  2. recognition that a filter function merely needs to return a truthy value if the argument is to be retained:

    def filter_func(char):
        return char == '\n' or 32 <= ord(char) <= 126
    # and later:
    filtered_data = filter(filter_func, data).lower()
    

这个答案对于那些像原帖一样提出类似问题的人来说非常有帮助,而且你提供的答案也很具有Python风格。然而,我发现在你解释的问题中(我经常遇到),并没有更高效的解决方案,逐个字符地处理在一个非常大的文件中需要很长时间。 - Xodarap777

3

我正在阅读《流畅的Python》(Ramalho)- 强烈推荐。 以下是受第二章启发的列表推导式一行代码:

onlyascii = ''.join([s for s in data if ord(s) < 127])
onlymatch = ''.join([s for s in data if s in
              'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'])

这将不允许标准ASCII符号,如项目符号、度数符号、版权符号、日元符号等。此外,您的第一个示例包括非可打印符号,如BELL,这是不可取的。 - SherylHohman

1
如果您想要可打印的 ASCII 字符,您可能需要更正您的代码为:

如果你想要可打印的 ASCII 字符,你可能需要更正你的代码为:

if ord(char) < 32 or ord(char) > 126: return ''

这相当于 string.printable(来自 @jterrace 的答案),除了没有回车和制表符 ('\t','\n','\x0b','\x0c' 和 '\r'),但不对应你的问题中的范围。


1
稍微简单一些:lambda x: 32 <= ord(x) <= 126 - jterrace
这与string.printable不同,因为它省略了string.whitespace,尽管这可能是OP想要的,这取决于像\n和\t这样的东西。 - jterrace
@jterrace 对的,包括空格(ord 32),但不包括回车和制表符。 - joaquin
是的,只是对“这相当于string.printable”的评论,但并不正确。 - jterrace
我编辑了答案,谢谢!如果你不仔细阅读,那么这个OP的问题会误导你。 - joaquin

-1

这是获取ASCII字符和清理代码的最佳方式,检查所有可能的错误。

from string import printable

def getOnlyCharacters(texts):
    _type = None
    result = ''
    
    if type(texts).__name__ == 'bytes':
        _type = 'bytes'
        texts = texts.decode('utf-8','ignore')
    else:
        _type = 'str'
        texts = bytes(texts, 'utf-8').decode('utf-8', 'ignore')

    texts = str(texts)
    for text in texts:
        if text in printable:
            result += text
            
    if _type == 'bytes':
        result = result.encode('utf-8')

    return result

text = '�Ahm�����ed Sheri��'
result = getOnlyCharacters(text)

print(result)
#input --> �Ahm�����ed Sheri��
#output --> Ahmed Sheri

1
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

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