用单个空格替换非ASCII字符


244

我需要用空格替换所有非ASCII(\ x00- \ x7F)字符。令我惊讶的是,这在Python中并不是一件容易的事,除非我丢失了一些东西。以下功能仅删除所有非ASCII字符:

def remove_non_ascii_1(text):

    return ''.join(i for i in text if ord(i)<128)

并且该字符将非ASCII字符替换为空格,该空格数量与字符代码点中的字节数相同(即,字符替换为3个空格):

def remove_non_ascii_2(text):

    return re.sub(r'[^\x00-\x7F]',' ', text)

如何用单个空格替换所有非ASCII字符?

无数 类似 SO 问题 地址 的字符 替换 反对 剥离进一步解决所有非ASCII字符不是一个特定的字符。


46
哇,您真的很努力地展示了这么多链接。每天续约+1!
shad0w_wa1k3r

3
您似乎已经错过了这个stackoverflow.com/questions/1342000/…–
Stuart

我对看到示例输入有问题很感兴趣。
dstromberg

5
@Stuart:谢谢,但这是我提到的第一个。
dotancohen

1
@dstromberg:我在问题中提到了一个有问题的示例字符:。是这个家伙
dotancohen

Answers:


243

您的''.join()表达式正在过滤,删除所有非ASCII内容;您可以改为使用条件表达式:

return ''.join([i if ord(i) < 128 else ' ' for i in text])

这将一个接一个地处理字符,每个替换字符仍将使用一个空格。

您的正则表达式应仅将连续的非ASCII字符替换为一个空格:

re.sub(r'[^\x00-\x7F]+',' ', text)

注意+那里。


18
@dstromberg:较慢;str.join() 需要一个列表(它将两次传递值),并且生成器表达式将首先转换为一个。赋予列表理解能力只是更快。看到这篇文章
马丁·彼得斯

1
如果您向其输入UTF-8字节字符串,则第一段代码将在每个字符中插入多个空格。
Mark Ransom

@MarkRansom:我是假设这是Python的3
的Martijn Pieters的

2
问题中的“ 用3个空格替换字符”表示输入是字节字符串(不是Unicode),因此使用Python 2(否则''.join将失败)。如果OP希望每个Unicode代码点有一个空格,则应首先将输入解码为Unicode。
jfs

这对我很有帮助!
穆罕默德·哈西卜

55

对于您来说,您可以获得原始字符串的最相似的表示形式,我建议使用unidecode模块

from unidecode import unidecode
def remove_non_ascii(text):
    return unidecode(unicode(text, encoding = "utf-8"))

然后,您可以在字符串中使用它:

remove_non_ascii("Ceñía")
Cenia

有趣的建议,但它假设用户希望非ascii成为unidecode的规则。但是,这向问问者提出了一个后续问题,即他们为什么坚持使用空格,也许用另一个角色代替?
jxramos

谢谢,这是一个很好的答案。对于这个问题,它不起作用,因为我要处理的大多数数据都没有类似ASCII的表示形式。如דותן。但是,从一般意义上讲,这很好,谢谢!
dotancohen

1
是的,我知道这不适用于问题,但是我登陆此地是为了解决该问题,所以我想我只想分享我自己的问题的解决方案,对于以@dotancohen交易的人来说,我认为这很常见始终使用非ASCII字符。
Alvaro Fuentes

过去有一些类似此类的安全漏洞。请小心执行此操作!
deweydb '16

似乎不适用于UTF-16编码的文本字符串
user5359531 2016年

22

对于字符处理,请使用Unicode字符串:

PythonWin 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32.
>>> s='ABC马克def'
>>> import re
>>> re.sub(r'[^\x00-\x7f]',r' ',s)   # Each char is a Unicode codepoint.
'ABC  def'
>>> b = s.encode('utf8')
>>> re.sub(rb'[^\x00-\x7f]',rb' ',b) # Each char is a 3-byte UTF-8 sequence.
b'ABC      def'

但是请注意,如果您的字符串包含分解的Unicode字符(例如,单独的字符和带重音符号的组合),您仍然会遇到问题:

>>> s = 'mañana'
>>> len(s)
6
>>> import unicodedata as ud
>>> n=ud.normalize('NFD',s)
>>> n
'mañana'
>>> len(n)
7
>>> re.sub(r'[^\x00-\x7f]',r' ',s) # single codepoint
'ma ana'
>>> re.sub(r'[^\x00-\x7f]',r' ',n) # only combining mark replaced
'man ana'

谢谢,这是一个重要的观察。如果您确实找到处理合并​​标记问题的合理方法,那么我很乐意为这个问题添加赏金。我想最好是简单地删除组合标记,而只保留未组合字符。
dotancohen

1
一种解决方案是使用ud.normalize('NFC',s)组合标记,但是并非所有组合组合都由单个代码点表示。您需要一个更智能的解决方案来查看ud.category()角色的特征。
Mark Tolonen

1
@dotancohen:Unicode中有一个“用户可感知的字符”概念,该概念可能跨越多个Unicode代码点。\X(扩展的字素簇)正则表达式(由regex模块支持)允许对此类字符进行迭代(注意:“字素不一定要组合字符序列,而组合的字符序列不一定是字素”)。
jfs

10

如果替换字符可以是“?” 而不是空格,那么我建议result = text.encode('ascii', 'replace').decode()

"""Test the performance of different non-ASCII replacement methods."""


import re
from timeit import timeit


# 10_000 is typical in the project that I'm working on and most of the text
# is going to be non-ASCII.
text = 'Æ' * 10_000


print(timeit(
    """
result = ''.join([c if ord(c) < 128 else '?' for c in text])
    """,
    number=1000,
    globals=globals(),
))

print(timeit(
    """
result = text.encode('ascii', 'replace').decode()
    """,
    number=1000,
    globals=globals(),
))

结果:

0.7208260721400134
0.009975979187503592

更换 ?如果需要的话,再加上另一个字符或空格,您的速度就会更快。
莫里茨

7

这个如何?

def replace_trash(unicode_string):
     for i in range(0, len(unicode_string)):
         try:
             unicode_string[i].encode("ascii")
         except:
              #means it's non-ASCII
              unicode_string=unicode_string[i].replace(" ") #replacing it with a single space
     return unicode_string

1
尽管这很不雅致,但可读性强。谢谢。
dotancohen

1
+1用于unicode处理... @dotancohen IMNSHO“可读”表示“实用”,增加了“优雅”,因此我要说“有点不优雅”
qneill

3

作为一种本机且高效的方法,您不需要使用ord字符或对其进行任何循环。只需使用进行编码,ascii然后忽略错误即可。

以下内容将只删除非ASCII字符:

new_string = old_string.encode('ascii',errors='ignore')

现在,如果要替换已删除的字符,请执行以下操作:

final_string = new_string + b' ' * (len(old_string) - len(new_string))

在python3中,这encode将返回一个字节字符串,因此请记住这一点。另外,此方法不会删除换行符等字符。
凯尔·吉布森

-1

可能会提出其他问题,但我正在提供@Alvero的答案(使用unidecode)。我想对字符串进行“常规”删除,即字符串的开头和结尾为空白字符,然后仅将其他空白字符替换为“常规”空格,即

"Ceñíaㅤmañanaㅤㅤㅤㅤ"

"Ceñía mañana"

def safely_stripped(s: str):
    return ' '.join(
        stripped for stripped in
        (bit.strip() for bit in
         ''.join((c if unidecode(c) else ' ') for c in s).strip().split())
        if stripped)

我们首先将所有非unicode空格替换为常规空格(然后重新加入),

''.join((c if unidecode(c) else ' ') for c in s)

然后,我们使用python的正常拆分方法再次拆分,并剥离每个“位”,

(bit.strip() for bit in s.split())

最后,再次将它们重新加入,但是只有当字符串通过if测试时,

' '.join(stripped for stripped in s if stripped)

然后,safely_stripped('ㅤㅤㅤㅤCeñíaㅤmañanaㅤㅤㅤㅤ')正确返回'Ceñía mañana'

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.