在Python中从字符串中删除控制字符。

68

我目前有以下代码

def removeControlCharacters(line):
    i = 0
    for c in line:
        if (c < chr(32)):
            line = line[:i - 1] + line[i+1:]
            i += 1
    return line

如果要删除的字符超过一个,这种方法就不起作用了。

10个回答

184

Unicode中有数百个控制字符。如果您正在清理来自Web或其他可能包含非ASCII字符的来源的数据,则需要使用Python的unicodedata模块unicodedata.category(…)函数返回任何字符的unicode类别代码(例如,控制字符、空格、字母等)。对于控制字符,类别始终以“C”开头。

此代码段从字符串中删除所有控制字符。

import unicodedata
def remove_control_characters(s):
    return "".join(ch for ch in s if unicodedata.category(ch)[0]!="C")

Unicode类别的示例:

>>> from unicodedata import category
>>> category('\r')      # carriage return --> Cc : control character
'Cc'
>>> category('\0')      # null character ---> Cc : control character
'Cc'
>>> category('\t')      # tab --------------> Cc : control character
'Cc'
>>> category(' ')       # space ------------> Zs : separator, space
'Zs'
>>> category(u'\u200A') # hair space -------> Zs : separator, space
'Zs'
>>> category(u'\u200b') # zero width space -> Cf : control character, formatting
'Cf'
>>> category('A')       # letter "A" -------> Lu : letter, uppercase
'Lu'
>>> category(u'\u4e21') # 両 ---------------> Lo : letter, other
'Lo'
>>> category(',')       # comma  -----------> Po : punctuation
'Po'
>>>

3
点赞,因为这是对于支持Unicode的应用程序唯一正确的答案。 - Will
1
最后一行不应该是:return "".join(ch for ch in s if unicodedata.category(ch)[0]!="C") 吗? - jilles de wit
2
这是一个非常可靠的解决方案,可以去除非打印字符,谢谢! - oski86
1
“Zl”类别也应该包括吗?我不太清楚U+2028到底是什么,但我刚刚不幸地遇到了它... - flow2k
这是唯一正确的答案。 - Ishan Kumar
1
这将删除的不仅仅是控制字符。重要的是,它还会删除Cn类别中的字符,这些字符只是“未分配”的字符(目前可用Unicode点的约75%)。通常,您也不希望在字符串中包含这些字符,但在某些情况下区别很重要。 - Indigenuity

31
你可以使用 str.translate 函数,并提供适当的映射关系,例如像这样:
>>> mpa = dict.fromkeys(range(32))
>>> 'abc\02de'.translate(mpa)
'abcde'

6
建议不要将 map 用作变量名称。 - Mark Byers
3
请注意,这会删除换行符。 - mlissner
4
这段代码无法运行。我一直收到TypeError: expected a character buffer object错误提示。Python 版本是 2.6。 - user1476056
5
@user1476056: 那您需要使用更新版本的Python。问题明确标记为python-3.x - SilentGhost
1
我认为应该是 dict.fromkeys(range(33)),因为 range 是上限排除的。 - dustinfarris
显示剩余3条评论

22

有兴趣匹配任何Unicode 控制字符 的正则表达式字符类,可以使用[\x00-\x1f\x7f-\x9f]

您可以像这样测试:

>>> import unicodedata, re, sys
>>> all_chars = [chr(i) for i in range(sys.maxunicode)]
>>> control_chars = ''.join(c for c in all_chars if unicodedata.category(c) == 'Cc')
>>> expanded_class = ''.join(c for c in all_chars if re.match(r'[\x00-\x1f\x7f-\x9f]', c))
>>> control_chars == expanded_class
True

因此,要使用re删除控制字符,请使用以下代码:

>>> re.sub(r'[\x00-\x1f\x7f-\x9f]', '', 'abc\02de')
'abcde'

3
与第一个答案的不同之处在于,这个只适用于 Cc,而那个适用于 C* - hyperknot

18

这是我所知道的最简单、最完整、最强大的方法。但是它需要一个外部依赖项,对于大多数项目来说,我认为这是值得的。

pip install regex

import regex as rx
def remove_control_characters(str):
    return rx.sub(r'\p{C}', '', 'my-string')

\p{C}Unicode字符属性,用于控制字符,所以你可以让Unicode负责决定哪些数百万可用的Unicode字符应被视为控制字符。还有其他非常有用的字符属性,例如\p{Z}表示任何类型的空格。


2
同意。与内置的unicodedata模块相比,regex库将具有最新的Unicode信息。 - scribu
顺便说一句,我会强烈建议避免像“import regex as re”这样的做法,特别是当它与Python标准库发生冲突时。这种方法会使代码中的依赖关系变得模糊,增加了不必要的混淆节点,降低了可读性。坚持使用“import regex”并在代码中使用“regex”可以使一切更加清晰明了。除此之外,对于这个答案,我点赞了。 - Chris Larson
1
@ChrisLarson 我同意!已更改。 - cmc
请问您能否写一下如何对数据框中的某一列进行操作?我尝试使用my_dataframe['column_1'].str.replace(r'\p{C}', ' ', regex=True)。但是出现了错误:“error: bad escape \p”。 - Syed Md Ismail
如果 my_dataframe['column_1'].str 是您的输入字符串,您可以使用 rx.sub(r'\p{C}', '', my_dataframe['column_1'].str) 返回已清理的字符串。 - cmc

9

您的实现存在问题,因为i的值不正确。但这并不是唯一的问题:它还反复使用缓慢的字符串操作,这意味着它的运行时间是O(n2)而不是O(n)。请尝试使用以下代码:

return ''.join(c for c in line if ord(c) >= 32)

2
жЇ”иµ· str.translate и‡іе°‘ж…ўдє†дё¤еЂЌгЂ‚ - SilentGhost
1
@ben:一切都很好,直到“ord”遇到非BMP字符。 - SilentGhost
ord()在非BMP字符上是否会出错?对我来说[ord(c) for c in u'\U00020000']运行正常,结果列表中的值都>=32,因为它们是代理对。 - Ben Hoyt
2
澄清一下:你是对的,ord(u'\U00020000') 在 Python 的 UCS2 版本中会失败,但在这种情况下使用 ord(c) 是可以的,因为迭代字符串总是会得到小于等于 65535 的字符。 - Ben Hoyt

7

对于Python 2,使用内置的translate函数:

import string
all_bytes = string.maketrans('', '')  # String of 256 characters with (byte) value 0 to 255

line.translate(all_bytes, all_bytes[:32])  # All bytes < 32 are deleted (the second argument lists the bytes to delete)

2
您在迭代期间修改了该行。类似这样的''.join([x for x in line if ord(x) >= 32])的代码。

2
filter(string.printable[:-5].__contains__,line)

仅返回仅限于ASCII字符集的翻译文本。 - SilentGhost

0

如果您只想删除特定的控制字符,可以这样做

line.replace("\x02", "")

\x02 是字符的代码,这里是STX(文本开始)。你可以在这里找到这些代码。


0

我尝试了上述所有方法,但都没有帮助。在我的情况下,我不得不删除Unicode的“LRM”字符:

最终,我找到了这个解决方案,它解决了我的问题:

df["AMOUNT"] = df["AMOUNT"].str.encode("ascii", "ignore")
df["AMOUNT"] = df["AMOUNT"].str.decode('UTF-8')

参考这里


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