什么是Python中去除字符串尾部空格的惯用方法?

3

这个函数的参数需要满足以下规则:

  1. 参数开头不能有任何空白字符
  2. 可能会有尾随空格
  3. 字符串中可能会有交错的空格。

目标:去除交错的重复空格并删除尾随空格。

这是我现在的做法:

# toks - a priori no leading space
def squeeze(toks):
  import re
  p = re.compile(r'\W+')
  a = p.split( toks ) 
  for i in range(0, len(a)):
    if len(a[i]) == 0:
      del a[i]
  return ' '.join(a) 

>>> toks( '  Mary  Decker   is hot   ' )
Mary Decker is hot

这能否被改进?足够Pythonic吗?


4
我可能还没有完全清醒:' '.join(test.split()) 有问题吗?(其中test是你的字符串。) - Ulrich Schwarz
4
你的参数违反了规则#1:它有前导空格。 - Fred Nurk
Ulrich: ' '.join() 会在 .split() 的空元素之间重新引入空格;而且 .split() 和 .split(' ') 是不同的。你至少要使用列表推导式来过滤掉 .split() 中的空结果。 - Jim Dennis
1
@Jim Dennis:如果没有任何参数,split()函数将在空格上进行分割,丢弃前导和尾随的空格,因此不会有空元素。 - Tim Pietzcker
@Jim Dennis:s.split() 产生的列表(这也是 Ulrich 使用的方法)永远不包含空元素。 - John Machin
6个回答

9
这是我的做法:
" ".join(toks.split())

PS. 这个问题里有潜在的暗示吗?;-)


.split() 不同于 .split(' '),他可能想要保留制表符、换行符和其他空白字符。 - Jim Dennis
2
没错,但问题并没有暗示他想要保留那些内容。看看这个例子。 - Keith
事实上,问题是“删除交错的重复空格”,Keith的答案是最合理的解释。 - John Machin
没有潜在信息。只是一个 Python 新手,试图不要自己给自己惹麻烦。那个解决方案非常优雅。 - Frankie Ribery

4
你不能使用 rstrip() 吗?
some_string.rstrip() 

在需要从字符串两侧删除空格时可以使用lstrip()和rstrip()方法,还可以使用strip()完成此操作。

此外:strip()方法还支持传入任意的字符作为参数进行去除。

string.strip = strip(s, chars=None)
    strip(s [,chars]) -> string

相关:如果您需要去除中间的空格:拆分字符串,去除术语并重新连接。

阅读API有助于理解!


1
那不会从中间删除多个空格。 - Keith
该问题涉及字符串末尾的空格 - 而不是字符串单词内部的尾随空格。 - user2665694
例子可能需要重新提问。 - Bruce

2

直接回答你的问题:

是的,它可以改进。第一个改进就是让它能够正常工作。

>>> squeeze('x    !    y')
'x y' # oops

问题1:当您应该使用\s+(空白字符)时,您正在使用\W+(非字字符)。
>>> toks = 'x  !  y  z  '
>>> re.split('\W+', toks)
['x', 'y', 'z', '']
>>> re.split('\s+', toks)
['x', '!', 'y', 'z', '']

问题2:删除空字符串的循环虽然可行,但只是偶然的。如果要编写通用的循环来原地删除空字符串,则需要倒序处理,否则下标i会与剩余元素数量不一致。这里之所以可行,是因为没有捕获组的re.split()方法只能在开头和结尾产生空元素。您已经排除了开始问题,并且末尾情况也不会引起问题,因为之前没有删除操作。因此,您只需要两行代码就可以替换掉这个非常丑陋的循环:
if a and not a[-1]: # guard against empty list
    del a[-1]

然而,除非您的字符串非常长并且担心速度问题(在这种情况下,您可能不应该使用re),否则您可能希望允许前导空格("我的数据没有前导空格"等断言通常被忽略),并直接在循环中完成它:

a = [x for x in p.split(toks) if x]

下一步是避免构建列表a

return ' '.join(x for x in p.split(toks) if x)

你提到了“Pythonic”……那么让我们放弃所有的re导入和编译开销,以及genxp,只需要这样做:

return ' '.join(toks.split())

1

嗯,如果我可以通过内置函数和特性合理地完成工作,我倾向于不使用re模块。例如:

def toks(s):
    return ' '.join([x for x in s.split(' ') if x])

...似乎只需使用内置的splitjoin和列表推导式来过滤分割字符串中的空元素就可以实现相同的目标。

这更符合“Pythonic”的风格吗?我认为是的。然而,我的观点并不具有权威性。

这也可以用lambda表达式来完成;但我认为那不符合Pythonic的风格。

顺便说一下,这假设您只想挤出重复的空格并修剪前导和尾随空格。如果您的意图是将所有空白序列混合成单个空格(并修剪前导和尾随),则将s.split(' ')更改为s.split()——不传递任何参数或Nonesplit()方法与传递一个空格是不同的。


是的,主题行说“尾随空格”。我已经解释了如何实现任一组语义。 - Jim Dennis
主题行通常不准确。在这种情况下,他详细的规范和代码清楚地表明他想要规范化内部空格。 - John Machin

0

我知道这个问题很老了。但为什么不使用正则表达式呢?

import re

result = '  Mary  Decker   is hot   '
print(f"=={result}==")

result = re.sub('\s+$', '', result)
print(f"=={result}==")

result = re.sub('^\s+', '', result)
print(f"=={result}==")

result = re.sub('\s+', ' ', result)
print(f"=={result}==")

输出结果为

==  Mary  Decker   is hot   ==
==  Mary  Decker   is hot==
==Mary  Decker   is hot==
==Mary Decker is hot==

0
为了让你的代码更具Python风格,你必须意识到在Python中,a[i]是一个字符串,而不是如果a[i]==''则删除a[i],最好的方法是如果a[i]!=''则保留a[i]
因此,不要使用以下方式:
def squeeze(toks):
    import re
    p = re.compile(r'\W+')
    a = p.split( toks )
    for i in range(0, len(a)):
        if len(a[i]) == 0:
            del a[i]
    return ' '.join(a)

def squeeze(toks):
    import re
    p = re.compile(r'\W+')
    a = p.split( toks )
    a = [x for x in a if x]
    return ' '.join(a)

然后

def squeeze(toks):
    import re
    p = re.compile(r'\W+')
    return ' '.join([x for x in p.split( toks ) if x])

接下来,需要考虑到一个函数可以接收生成器和列表:

def squeeze(toks):
    import re
    p = re.compile(r'\W+')
    return ' '.join((x for x in p.split( toks ) if x))

而且双括号不是必须的:

def squeeze(toks):
    import re
    p = re.compile(r'\W+')
    return ' '.join(x for x in p.split( toks ) if x)

.

.

此外,与其让 Python 每次调用函数 squeeze() 时都去验证命名空间中是否存在 re(这就是它的工作方式),不如将 re 作为默认参数传递进去更好:
import re
def squeeze(toks,re = re):
    p = re.compile(r'\W+')
    return ' '.join(x for x in p.split( toks ) if x)

而且,更好的是:

import re
def squeeze(toks,p = re.compile(r'\W+')):
    return ' '.join(x for x in p.split( toks ) if x)

.

.

注意:表达式中的if x部分仅有在列表p.split( toks )以空格开头和结尾时才能将标题''和结束''分离出来。
然而,与其进行分割,保留所需内容同样好。
import re
def squeeze(toks,p = re.compile(r'\w+')):
    return ' '.join(p.findall(toks))

.

.

所有这些说法,你问题中的模式r'\W+'是不适合你的目的的,正如John Machin所指出的。

如果你想压缩内部空格并删除尾随空格,其中空格被纯粹地定义为字符集' ' , '\f' , '\n' , '\r' , '\t' , '\v'(请参见re中的\s),你必须使用以下替换来进行分割:

import re
def squeeze(toks,p = re.compile(r'\s+')):
    return ' '.join(x for x in  p.split( toks ) if x)

或者,保留正确的子字符串:

import re
def squeeze(toks,p = re.compile(r'\S+')):
    return ' '.join(p.findall(toks))

这实际上就是更简单和更快的表达式' '.join(toks.split())

但如果你只想压缩内部并删除尾随字符' ''\t',同时保留换行符不变,则可以使用

import re
def squeeze(toks,p = re.compile(r'[^ \t]+')):
    return ' '.join(p.findall(toks))

而且这不能被其他任何东西所替代。


@John Machin 我道歉,我犯了一个遗漏。当然,空白是指空格符。我在(http://docs.python.org/library/re.html#module-re)中的“\s”描述中获取了列表。 - eyquem
@eyquem:不,你没有——仔细再读一遍:“[ \t\n\r\f\v]” - John Machin
@John Machin 你的意思是什么?你想让我写:_空格被纯粹地理解为指代字符集合中的每个字符,包括' ','\f','\n','\r','\t','\v'_吗? - eyquem
@eyquem:不,我假设“我拿了列表”最初发生了。但是你可能是指它在修复时间发生的。抱歉。 - John Machin
@John Machin 当我写下“我采用了文档中\s列表的描述”,我的意思是当我想知道这个称谓“whitespace”究竟涵盖哪些字符时,我在Python模块re的文档中找到了它们。我本可以写“我参考了我在文档中找到的whitespace的定义”。这并不意味着我进行了复制和粘贴。实际上,我根据自己的记忆(而不是RAM :)))以不同于文档的顺序编写了相关字符的列表或集合,因为我使用了一种记忆方法来回忆它们... - eyquem
显示剩余2条评论

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