在Python中从字符串中删除特定字符

714
我正在尝试使用Python从字符串中删除特定字符。这是我现在正在使用的代码。不幸的是,它似乎对字符串没有任何影响。
for char in line:
    if char in " ?.!/;:":
        line.replace(char,'')

我该如何正确地做这件事?


请参见为什么调用字符串方法(例如.replace或.strip)不会修改(变异)字符串?以获取有关此方法存在何种问题的特定调试问题。这里的答案主要集中在如何解决问题上。


37
已经过去5年了,但是如何使用filter函数和Lambda表达式:filter(lambda ch: ch not in " ?.!/;:", line)。我认为这非常简洁高效。当然,它会返回一个新的字符串,你需要给它分配一个变量名。 - John Red
5
实际上,它返回一个迭代器,该迭代器返回字符列表,但如果您把这个放在答案中,我们中的一些人会很高兴为其点赞。 - Bill Bell
3
@BillBell:请查看 https://dev59.com/12865IYBdhLWcg3wLrlE#46930314 和 https://dev59.com/12865IYBdhLWcg3wLrlE#41413861。 - serv-inc
@BillBell:PS:在Python3中它是一个迭代器,在Python2中它是一个字符串、元组或列表。 - serv-inc
26个回答

772
Python中的字符串是不可变的(不能被更改)。因此,line.replace(...) 的效果只是创建一个新的字符串,而不是更改旧的字符串。您需要将其重新绑定(分配)给line,以使该变量获取新值,并删除那些字符。
另外,您这样做的方式相对来说会比较慢。对于有经验的Python开发者来说,这也可能有点令人困惑,他们会看到一个双重嵌套的结构,并认为正在进行更复杂的操作。
从Python 2.6和更新的Python 2.x版本开始*,您可以使用str.translate,(请参见下面的Python 3答案):
line = line.translate(None, '!@#$')

或使用 re.sub 进行正则表达式替换。

import re
line = re.sub('[!@#$]', '', line)

方括号内的字符构成一个“字符类”。在line中属于该类的任何字符都将被替换为sub方法的第二个参数:一个空字符串。

Python 3答案

在Python 3中,字符串是Unicode编码的。您需要进行一些不同的翻译。kevpie在其中一个答案的comment中提到了这一点,在str.translate的文档中也有说明。

当调用Unicode字符串的translate方法时,您不能像上面那样传递第二个参数。您也不能将None作为第一个参数传递。相反,您只能将一个翻译表(通常是字典)作为唯一参数传递。该表将字符的序数值(即对它们调用ord的结果)映射到应该替换它们的字符的序数值,或者 - 对我们很有用 - 映射到None以表示它们应该被删除。

因此,要使用Unicode字符串执行上述操作,您需要调用类似于以下内容的代码:

translation_table = dict.fromkeys(map(ord, '!@#$'), None)
unicode_line = unicode_line.translate(translation_table)

这里使用dict.fromkeysmap来简洁地生成一个包含的字典。

{ord('!'): None, ord('@'): None, ...}

更加简单的方法,就像另一个答案所说的那样,在原地创建翻译表:
unicode_line = unicode_line.translate({ord(c): None for c in '!@#$'})

或者,就像Joseph Lee提出的那样,使用str.maketrans创建相同的翻译表:

unicode_line = unicode_line.translate(str.maketrans('', '', '!@#$'))

* 为了与早期版本的Python兼容,您可以创建一个“空”翻译表来替换None

import string
line = line.translate(string.maketrans('', ''), '!@#$')

这里使用string.maketrans创建了一个“翻译表”,它只是一个包含0到255的字符序号值的字符串。

30
在Python3中,line.translate只接受一个参数,第一个解决方案行不通。 - marczoid
43
在Python3中,str.translate()方法不接受第二个参数。因此,你的答案应该是line.translate({ord(i):None for i in '!@#$'}) - naveen
1
和其他字符一样。Python允许您使用单引号或双引号中的任意一对。因此,您只需编写"'"即可设置该字符。 - intuited
2
@naveen上面的评论对我很有用。Pythony 2.7.13。在我的情况下,我想去掉“和'字符:notes = notes.translate({ord(i):None for i in '\"\''}) - RyanG
1
在Python 3中,您可以使用unicode_line.translate(str.maketrans('','','!@#$'))unicode_line.translate(dict.fromkeys(map(ord, '!@#$'))). - Martijn Pieters
显示剩余4条评论

339

我是否理解有误,还是只是以下内容让我感到困惑:

string = "ab1cd1ef"
string = string.replace("1", "") 

print(string)
# result: "abcdef"

将其放到循环中:

a = "a!b@c#d$"
b = "!@#$"
for char in b:
    a = a.replace(char, "")

print(a)
# result: "abcd"

37
每次循环都复制了一遍字符串,这可能不是想要的。此外,这不是很好的 Python 做法。在 Python 中,你应该像这样循环:for char in b: a=a.replace(char,"") - elgehelge
1
为了更高效地运行,将b中所有字符放入一个set中,初始化一个空列表,然后迭代a,如果当前字符不在set中,则将其添加到列表中。完成后,您可以将列表转换回字符串。线性时间和线性(额外)空间。 - kebab-case
当然,这个方法可行,但它的时间复杂度为O(n^2),其中n = len(string) - DonCarleone

64
>>> line = "abc#@!?efg12;:?"
>>> ''.join( c for c in line if  c not in '?:!/;' )
'abc#@efg12'

使用另一个字符串分隔符,例如'''或"。 - ALisboa
2
如果你有很多被禁止的字符,你可以通过先将其转换为集合来加快代码速度。blacklist = set('?:!/;') 然后使用 ''.join(c for c in line if c not in blacklist) - user3064538

63

使用re.sub正则表达式

自Python 3.5以来,可以使用正则表达式re.sub进行替换:

import re
re.sub('\ |\?|\.|\!|\/|\;|\:', '', line)

例子

import re
line = 'Q: Do I write ;/.??? No!!!'
re.sub('\ |\?|\.|\!|\/|\;|\:', '', line)

'QDoIwriteNo'

解释

正则表达式中,|表示逻辑或,\用于转义可能是实际正则表达式命令的空格和特殊字符。而sub代表替换(substitution),在这种情况下是用空字符串''进行替换。


1
@vitaliis 如果您还想删除换行符和回车符,请将 re.sub() 中的第一个字符串替换为 '\ |\?|\.|\!|\/|\;|\:|\n|\r' - Serge Stroobandt

25

如果你需要在一个字符串中仅允许某些字符,你可以使用正则表达式并带有一个集合补集操作符[^ABCabc]。例如,要删除除ASCII字母、数字和连字符之外的所有内容:

>>> import string
>>> import re
>>>
>>> phrase = '  There were "nine" (9) chick-peas in my pocket!!!      '
>>> allow = string.letters + string.digits + '-'
>>> re.sub('[^%s]' % allow, '', phrase)

'Therewerenine9chick-peasinmypocket'

来自Python正则表达式文档

不在某一范围内的字符可以通过对该范围取反来匹配。如果集合的第一个字符是'^',那么所有不在集合中的字符都将被匹配。例如,[^5] 将匹配除字符'5'外的任何字符,[^^] 将匹配除'^'外的任何字符。如果^ 不是集合中的第一个字符,则没有特殊含义。


25

提问者已经接近答案了。像 Python 中的大多数事情一样,答案比你想象的要简单。

>>> line = "H E?.LL!/;O:: "  
>>> for char in ' ?.!/;:':  
...  line = line.replace(char,'')  
...
>>> print line
HELLO

你不必使用嵌套的if/for循环,但是你确实需要逐个检查每个字符。


是的,我知道,可能有点晚了,但如果你转义它应该可以工作。 像这样: line = line.replace('`', '')继续阅读: https://learnpythonthehardway.org/book/ex10.html - Aiyion.Prime
1
这可能不是高效的,因为你为每个字符分配了一个新的字符串。 - OneCricketeer

15
line = line.translate(None, " ?.!/;:")

2
+1 当使用Unicode时,需要设置一个翻译来删除而不是删除字符串。http://docs.python.org/library/stdtypes.html#str.translate - kevpie
这是一个很好的建议(参考:https://docs.python.org/2/library/string.html#string.translate)。Unicode注释也很不错。 - cgseller
5
类型错误:translate()需要恰好一个参数(给定2个)。 - Adnan Ali

13
>>> s = 'a1b2c3'
>>> ''.join(c for c in s if c not in '123')
'abc'

2
我的回答确实提供了原问题的解决方案,但我也对为什么我的解决方案可能不理想感到感兴趣(也许OP也是如此)。我是否应该创建一个新问题并引用这个问题作为背景? - eatkin

12

在Python中,字符串是不可变的。 replace 方法在替换后返回一个新字符串。尝试:

for char in line:
    if char in " ?.!/;:":
        line = line.replace(char,'')

这与您的原始代码完全相同,只是在循环内添加了对line的赋值。

请注意,字符串的replace()方法会替换字符串中所有出现的字符,因此您可以通过对要删除的每个字符使用replace()而不是循环遍历字符串中的每个字符来更好地处理。


你如何在迭代行时同时修改它? - eumiro
1
@eumiro:迭代过程在原始的“line”上进行。 - Greg Hewgill
好知道!所以如果我遍历一个数组,我就是在遍历原始数组。无法遍历迭代器。 - eumiro
2
这非常浪费。您遍历line的每个字符,并检查该字符是否在要删除的字符集中。如果是,则删除line中所有其出现的情况,那么为什么要继续遍历line的其余字符并再次检查已经不可能存在的字符?相反,我建议使用以下代码:for char in " ?.!/;:": line = line.replace(char, "") 这将有与要删除的字符数一样多的迭代次数。更易读的版本在此处 - pfabri

10

我很惊讶没有人推荐使用内置的filter函数。

    import operator
    import string # only for the example you could use a custom string

    s = "1212edjaq"

假设我们想要过滤掉非数字的内容。使用filter内置方法"…相当于生成器表达式(item for item in iterable if function(item))" [Python 3内置函数:Filter]

    sList = list(s)
    intsList = list(string.digits)
    obj = filter(lambda x: operator.contains(intsList, x), sList)))

在Python 3中,这将返回:
    >>  <filter object @ hex>

要获得一个打印的字符串,
    nums = "".join(list(obj))
    print(nums)
    >> "1212"

我不确定filter在效率方面排名如何,但在进行列表推导等操作时,学会使用它是一件好事。
更新
从逻辑上讲,由于filter可行,您也可以使用列表推导。据我所知,这应该更有效,因为lambda是编程函数世界的华尔街对冲基金经理。另外一个优点是它是一个不需要任何导入的单行代码。例如,使用上面定义的相同字符串 's':
      num = "".join([i for i in s if i.isdigit()])

这就是全部内容。返回的将是原始字符串中所有数字字符构成的字符串。
如果您有一个特定的可接受/不可接受字符列表,您只需要调整列表推导式中的“if”部分即可。
      target_chars = "".join([i for i in s if i in some_list]) 

或者,另外一种选择是,
      target_chars = "".join([i for i in s if i not in some_list])

如果你已经在使用 lambda,那么使用 operator.contains 没有任何意义。应该将 lambda x: operator.contains(intsList, x) 改为 lambda x: x in intsList,或者如果你想要获得 C 级别的检查,可以直接使用 intsList.__contains__(完全不需要 lambda)。 - ShadowRanger

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