有没有简单的方法可以在字符串中删除多个空格?

683

假设这个字符串:

The   fox jumped   over    the log.

转化为:

The fox jumped over the log.

如何在不拆分并转成列表的情况下,用最简单的方式(1-2行代码)实现这个功能?


31
你对列表有什么反感吗?它们是语言的重要组成部分," ".join(list_of_words) 是将字符串列表合并为一个以空格分隔的字符串的核心习语之一。 - PaulMcG
4
@Tom/@Paul:对于简单的字符串,(字符串)连接可能是简单而甜美的。但如果存在其他不想打扰的空格,则会变得更加复杂...在这种情况下,“while”或正则表达式解决方案最好。我在下面发布了一个“正确”的字符串连接方法,并列出了使用三种方法进行此操作的计时测试结果。 - pythonlarry
27个回答

893
>>> import re
>>> re.sub(' +', ' ', 'The     quick brown    fox')
'The quick brown fox'

42
该解决方案仅处理单个空格字符。它不会替换制表符或其他由\s处理的空白字符,如nsr81的解决方案中所示。 - Taylor Leese
3
没错,string.split 也可以处理各种类型的空格。 - Josh Lee
10
我更喜欢这个选项,因为它只关注空格字符,不会影响像"\n"这样的字符。 - hhsaffar
42
你可以使用re.sub(' {2,}', ' ', 'The quick brown fox')防止将单个空格重复替换为单个空格 - AneesAhmed777
8
对于那些懒得读完所有答案的人,re.sub('\s{2,}', ' ', text) 将会用单个空格替换掉所有连续的空白字符(制表符、换行符等)。 - otaku
显示剩余10条评论

832

foo 是你的字符串:

" ".join(foo.split())

请注意,这将删除所有空格字符(空格、制表符、换行符、回车符、换页符)(感谢hhsaffar的评论)。也就是说,"this is \t a test\n"最终将变成"this is a test"


31
不分割成列表并且不详细列举…… - Gumbo
121
我忽略了“不将其拆分为列表…”,因为我仍然认为这是最好的答案。 - Taylor Leese
8
比re.sub()方案快6倍。 - nerdfever.com
5
乍一看,这行代码的作用并不明显。其他人会很难弄清楚为什么要将字符串拆分并重新连接。使用正则表达式的答案更明确地说明了它的作用。 - Jeyekomon
6
我不确定原帖作者避免分割和合并的原因,但我可以告诉你我的理由:当使用大型数据框时,分割+重新合并会占用大量内存导致程序崩溃。(我使用PythonAnywhere,他们终止了该进程。)而re.sub则不会这样。 - larapsodia
显示剩余9条评论

135
import re
s = "The   fox jumped   over    the log."
re.sub("\s\s+" , " ", s)
或者
re.sub("\s\s+", " ", s)

由于在PEP 8中,逗号前的空格被列为一个“宠物狂热”,该评论中提到了用户Martin Thoma


3
我倾向于将正则表达式更改为 r"\s\s+",以便它不会尝试替换已经是单个空格的情况。 - Ben Blank
28
如果你想要那种行为,为什么不直接使用 "\s{2,}" 而不是为了避免不知道中等难度的正则表达式行为而采用一个变通方法呢? - Chris Lutz
3
请记住,sub() 不会改变输入字符串s,而是返回一个新的值。 - gcb
17
我建议避免使用\s\s+,因为这种方式无法将TAB字符标准化为普通空格。 使用空格和制表符的组合会被替换成普通空格。 - vdboor
4
在执行此操作之前,我建议您使用 strip() 函数(也称为修剪)来删除字符串中的空格,因为您可能不希望保留字符串开头和结尾的空格。 - Christophe Roussy
显示剩余6条评论

65
使用正则表达式中的"\s"和简单的string.split()也会删除其他空格 - 如换行符、回车符、制表符等。除非这是所期望的,只需要处理多个空格,否则请参考以下示例。
我使用11段落,1000个单词,6665字节的Lorem Ipsum进行实际时间测试,并在其中添加了随机长度的额外空格。
original_string = ''.join(word + (' ' * random.randint(1, 10)) for word in lorem_ipsum.split(' '))

这个一行代码实际上会去除任何前导/尾随空格,并保留一个前导/尾随空格(但只有 一个 ;-)。
# setup = '''

import re

def while_replace(string):
    while '  ' in string:
        string = string.replace('  ', ' ')

    return string

def re_replace(string):
    return re.sub(r' {2,}' , ' ', string)

def proper_join(string):
    split_string = string.split(' ')

    # To account for leading/trailing spaces that would simply be removed
    beg = ' ' if not split_string[ 0] else ''
    end = ' ' if not split_string[-1] else ''

    # versus simply ' '.join(item for item in string.split(' ') if item)
    return beg + ' '.join(item for item in split_string if item) + end

original_string = """Lorem    ipsum        ... no, really, it kept going...          malesuada enim feugiat.         Integer imperdiet    erat."""

assert while_replace(original_string) == re_replace(original_string) == proper_join(original_string)

#'''

# while_replace_test
new_string = original_string[:]

new_string = while_replace(new_string)

assert new_string != original_string

# re_replace_test
new_string = original_string[:]

new_string = re_replace(new_string)

assert new_string != original_string

# proper_join_test
new_string = original_string[:]

new_string = proper_join(new_string)

assert new_string != original_string

注意: "while版本"会复制original_string,因为我认为修改后的第一次运行后,后续的运行将更快(即使只有一点点)。由于这会增加时间,所以我在其他两种方式中也添加了字符串副本,以便时间只显示逻辑上的差异。 请记住,在timeit实例上主要的stmt语句只会执行一次; 我最初的做法是,while循环在同一个标签original_string上工作,因此第二次运行时,没有什么可以做的。现在它的设置方式是调用函数,使用两个不同的标签,这不是问题。为了证明我们每次迭代都会改变某些内容(对于那些可能持怀疑态度的人),我已经向所有的工作者添加了assert语句。例如,更改如下内容即会导致其出错:

# while_replace_test
new_string = original_string[:]

new_string = while_replace(new_string)

assert new_string != original_string # will break the 2nd iteration

while '  ' in original_string:
    original_string = original_string.replace('  ', ' ')

Tests run on a laptop with an i5 processor running Windows 7 (64-bit).

timeit.Timer(stmt = test, setup = setup).repeat(7, 1000)

test_string = 'The   fox jumped   over\n\t    the log.' # trivial

Python 2.7.3, 32-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001066 |   0.001260 |   0.001128 |   0.001092
     re_replace_test |   0.003074 |   0.003941 |   0.003357 |   0.003349
    proper_join_test |   0.002783 |   0.004829 |   0.003554 |   0.003035

Python 2.7.3, 64-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001025 |   0.001079 |   0.001052 |   0.001051
     re_replace_test |   0.003213 |   0.004512 |   0.003656 |   0.003504
    proper_join_test |   0.002760 |   0.006361 |   0.004626 |   0.004600

Python 3.2.3, 32-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001350 |   0.002302 |   0.001639 |   0.001357
     re_replace_test |   0.006797 |   0.008107 |   0.007319 |   0.007440
    proper_join_test |   0.002863 |   0.003356 |   0.003026 |   0.002975

Python 3.3.3, 64-bit, Windows
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.001444 |   0.001490 |   0.001460 |   0.001459
     re_replace_test |   0.011771 |   0.012598 |   0.012082 |   0.011910
    proper_join_test |   0.003741 |   0.005933 |   0.004341 |   0.004009

test_string = lorem_ipsum
# Thanks to http://www.lipsum.com/
# "Generated 11 paragraphs, 1000 words, 6665 bytes of Lorem Ipsum"

Python 2.7.3, 32-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.342602 |   0.387803 |   0.359319 |   0.356284
     re_replace_test |   0.337571 |   0.359821 |   0.348876 |   0.348006
    proper_join_test |   0.381654 |   0.395349 |   0.388304 |   0.388193    

Python 2.7.3, 64-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.227471 |   0.268340 |   0.240884 |   0.236776
     re_replace_test |   0.301516 |   0.325730 |   0.308626 |   0.307852
    proper_join_test |   0.358766 |   0.383736 |   0.370958 |   0.371866    

Python 3.2.3, 32-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.438480 |   0.463380 |   0.447953 |   0.446646
     re_replace_test |   0.463729 |   0.490947 |   0.472496 |   0.468778
    proper_join_test |   0.397022 |   0.427817 |   0.406612 |   0.402053    

Python 3.3.3, 64-bit
                test |      minum |    maximum |    average |     median
---------------------+------------+------------+------------+-----------
  while_replace_test |   0.284495 |   0.294025 |   0.288735 |   0.289153
     re_replace_test |   0.501351 |   0.525673 |   0.511347 |   0.508467
    proper_join_test |   0.422011 |   0.448736 |   0.436196 |   0.440318

对于简单的字符串,最快的方法似乎是使用while循环,其次是Pythonic字符串拆分/连接,最后是正则表达式。

对于非平凡的字符串,似乎需要考虑更多因素。32位2.7?正则表达式来拯救!2.7 64位?使用while循环效果最佳,优势明显。32位3.2,选择“适当”的join。64位3.3,使用while循环。再次。

最终,可以在需要的时候改善性能,但最好记住口头禅

  1. 让它工作
  2. 让它正确
  3. 让它快速

本人不是法律专家,你的情况可能有所不同,请自行斟酌!


4
жҲ‘жӣҙеёҢжңӣдҪ жөӢиҜ•з®ҖеҚ•зҡ„' '.join(the_string.split())пјҢеӣ дёәиҝҷжҳҜйҖҡеёёзҡ„з”Ёжі•пјҢдҪҶжҲ‘иҝҳжҳҜиҰҒж„ҹи°ўдҪ зҡ„е·ҘдҪңпјҒ - wedi
@wedi:根据其他评论(例如Gumbouser984003,尽管她/他的解决方案是主观的,不能在“所有情况”下工作),这种解决方案不符合提问者的要求。可以使用.split(' ')和comp/gen,但处理前导/尾随空格会更加棘手。 - pythonlarry
@wedi: 例如:' '.join(p for p in s.split(' ') if p) <-- 仍会丢失前导/尾随空格,但考虑到多个空格。要保留它们,必须像这样操作:parts = s.split(' '); (' ' if not parts[0] else '') + ' '.join(p for p in s.split(' ') if p) + (' ' if not parts[-1] else '') - pythonlarry
@pythonlarry 我添加了两个额外版本的答案。 - Lee
你为什么把它做得这么复杂? - Raymond
显示剩余3条评论

60

我必须同意Paul McGuire的评论。对于我来说,

' '.join(the_string.split())

使用split-then-join方法要比使用正则表达式更为可取。

我的测试结果(在Linux系统下,Python版本为2.5)表明,使用split-then-join方法几乎比使用“re.sub(...)”快五倍,并且如果你预编译一次正则表达式然后多次操作,仍然比其快三倍。而且使用split-then-join方法更易于理解,更符合Python语言风格。


这将删除末尾的空格。如果您想保留它们,请执行以下操作:text[0:1] + " ".join(text[1:-1].split()) + text[-1] - user984003
4
使用简单的正则表达式更易于阅读。在必要之前不要优化性能。 - gcb
@gcb:为什么不呢?如果你预计会有高吞吐量的情况(例如因为需求量很高),为什么不在一开始就部署一些你认为资源消耗较少的东西呢? - Hassan Baig
1
@HassanBaig 如果你已经有了性能要求,那么这就不算是过早优化了,对吧?我的观点是,当你还不需要过于关注性能时,以可读性为目标总是更好的选择。 - gcb
2
@gcb“在需要之前,永远不要为性能优化”,听起来像是IT业务的一种盈利模式。而且我发现一个简单的 正则表达式 更难读,所以对我来说这是一个偏好问题。 Splitjoin 很常用,而且不需要额外导入模块。当涉及到软件时,应该有远见并准备好某些场景。大多数开发人员会犯这个错误,客户过一段时间后开始抱怨。软件必须能够处理已知和未知的情况。速度、稳定性、安全性和->统一性比可读性更重要。 - FifthAxiom
只需去除连续重复的空格,可以使用 ' '.join(the_string.split(' '))。这也将删除前导和尾随空格,但将保留换行符、制表符等。 - FifthAxiom

21

与之前的解决方案类似,但更加具体:将两个或多个空格替换为一个:

>>> import re
>>> s = "The   fox jumped   over    the log."
>>> re.sub('\s{2,}', ' ', s)
'The fox jumped over the log.'

2
你为什么在重复答案? - Raymond

18

我尝试了以下方法,它甚至可以处理极端情况,例如:

str1='          I   live    on    earth           '

' '.join(str1.split())

但如果你更喜欢使用正则表达式,它也可以这样写:

re.sub('\s+', ' ', str1)

虽然需要进行一些预处理才能去除末尾和结尾的空格。


可以通过 str1.strip() 轻松地去除前导和尾随空格,然后将其传递给 re.sub() 函数,如下所示 re.sub(' +', ' ', str1.strip()) - Youstanzr

18
import re

Text = " You can select below trims for removing white space!!   BR Aliakbar     "
  # trims all white spaces
print('Remove all space:',re.sub(r"\s+", "", Text), sep='') 
# trims left space
print('Remove leading space:', re.sub(r"^\s+", "", Text), sep='') 
# trims right space
print('Remove trailing spaces:', re.sub(r"\s+$", "", Text), sep='')  
# trims both
print('Remove leading and trailing spaces:', re.sub(r"^\s+|\s+$", "", Text), sep='')
# replace more than one white space in the string with one white space
print('Remove more than one space:',re.sub(' +', ' ',Text), sep='') 

结果:作为代码

"Remove all space:Youcanselectbelowtrimsforremovingwhitespace!!BRAliakbar"
"Remove leading space:You can select below trims for removing white space!!   BR Aliakbar"     
"Remove trailing spaces: You can select below trims for removing white space!!   BR Aliakbar"
"Remove leading and trailing spaces:You can select below trims for removing white space!!   BR Aliakbar"
"Remove more than one space: You can select below trims for removing white space!! BR Aliakbar" 

16

一个简单的解决方案

>>> import re
>>> s="The   fox jumped   over    the log."
>>> print re.sub('\s+',' ', s)
The fox jumped over the log.

11
您还可以在Pandas DataFrame中使用字符串拆分技术,而无需使用.apply(..),如果您需要在大量字符串上快速执行操作,则这非常有用。 这是一行代码实现:
df['message'] = (df['message'].str.split()).str.join(' ')

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