从字符串列表中删除空字符串

962
我想从Python的字符串列表中删除所有空字符串。
我的想法看起来像这样:
while '' in str_list:
    str_list.remove('')

有没有更符合 Python 风格的方法来完成这个任务?


50
@Ivo,这两个陈述都不正确。在使用for x in list迭代列表时,您永远不应该修改该列表。如果您使用while loop,则可以这样做。演示的循环将删除空字符串,直到没有更多的空字符串为止,然后停止。实际上,我甚至没有看问题(只看了标题),但我给出了完全相同的循环作为可能性!如果您不想出于内存考虑使用 comprehensions 或 filters,则这是一种非常 Pythonic 的解决方案。 - aaronasterling
4
永远不要改变你正在迭代的列表,这仍然是一个非常有效的观点 :) - Eduard Luca
1
@EduardLuca 如果迭代列表的目的是为了更改它,那么你应该做的恰恰相反。你只需要小心,确保你不会因此引起意外行为。 - Jacqlyn
1
@EduardLuca, @JFA:重点是他没有遍历任何列表。如果他写成了for var in list:的形式,那么他会进行遍历。但是在这里,他写成了while const in list:的形式。这并不是在遍历任何内容,它只是在满足条件为假之前一直重复相同的代码。 - Camion
1
您可以使用过滤器来移除空字符串。代码应该长这个样子… data = list(filter(None, str_list)) - Jacob Ward
13个回答

1533

我会使用filter函数:

str_list = filter(None, str_list)
str_list = filter(bool, str_list)
str_list = filter(len, str_list)
str_list = filter(lambda item: item, str_list)

Python 3从filter返回一个迭代器,所以应该用list()进行包装。

str_list = list(filter(None, str_list))

19
如果你非常注重性能,那么itertools模块中的ifilter函数是更快的选择—— >>> timeit('filter(None, str_list)', 'str_list=["a"]*1000', number=100000) 得到结果 2.3468542098999023; >>> timeit('itertools.ifilter(None, str_list)', 'str_list=["a"]*1000', number=100000) 得到结果 0.04442191123962402 - Humphrey Bogart
4
非常正确。但是使用ifilter时,结果是惰性地评估的,而不是一次性评估——我认为对于大多数情况来说,ifilter更好。有趣的是,使用filter仍然比在ifilter中包装一个list要快。 - Humphrey Bogart
8
如果你对一个数字列表执行此操作,请注意零也将被删除(注:我只使用了前三种方法),因此你需要使用另一种方法。 - SnoringFrog
3
这句话的重点只在于速度,而不关注解决方案是否符合Pythonic风格(所问的问题)。列表推导式是Pythonic风格的解决方案,只有在剖析证明列表推导式存在瓶颈时才应使用过滤器。 - Tritium21
3
请编辑并更新回答,如果有人提到或暗示Python 3。当问这个问题时,我们只讨论了Python 2,即使Python 3已经发布了将近2年。但请同时更新Python 2和3的结果。 - livibetter
显示剩余8条评论

427

使用列表推导式是最符合Python风格的方式:

>>> strings = ["first", "", "second"]
>>> [x for x in strings if x]
['first', 'second']
如果必须在原地修改列表,因为还有其他引用需要看到更新后的数据,则使用切片赋值:
strings[:] = [x for x in strings if x]

40
我喜欢这个解决方案,因为它很容易适应不同的需求。比如,如果我需要移除只有空格但没有其他字符的字符串,我可以这样写: [x for x in strings if x.strip()] - Bond
1
[x for x in strings if x] 这个代码可以正常运行,但请解释一下这个循环是如何工作的? - Amar Kumar
8
在Python中,当空字符串在布尔上下文中使用时(例如在 if x 中),它们会被视为假。方括号、for 循环和 if 子句组合起来的含义是:“如果 x 实际上包含某些内容,则为 strings 中的每个元素生成一个由 x 组成的列表。” @Ib33x 做得非常棒。这个答案绝对是最符合Python风格的。 - Nat Riddle
很好。[x for x in strings if x.strip()] 可以去除空格字符串。 - PatrickT

109

在此情况下,筛选器实际上具有特殊选项:

filter(None, sequence)

它将过滤掉所有评估为False的元素。在这里不需要使用实际的可调用对象(如bool、len等)。

它和map(bool, ...)一样快。


9
这实际上是一个 Python 程序员的惯用语,但这也是我仍然会使用 filter() 的唯一情况,因为在其他地方都已经被列表推导式取代了。 - kaleissin
我发现这种方式更容易看出代码的意图,相比于列表推导式。 - Martin CR

35
>>> lstr = ['hello', '', ' ', 'world', ' ']
>>> lstr
['hello', '', ' ', 'world', ' ']

>>> ' '.join(lstr).split()
['hello', 'world']

>>> filter(None, lstr)
['hello', ' ', 'world', ' ']

比较时间

>>> from timeit import timeit
>>> timeit('" ".join(lstr).split()', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
4.226747989654541
>>> timeit('filter(None, lstr)', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
3.0278358459472656

请注意,filter(None, lstr)不能移除只含有空格' '的字符串,它只能删除'',而' '.join(lstr).split()则都可以移除。

如果要使用带有去除空格字符串的filter(),会需要更多时间:

>>> timeit('filter(None, [l.replace(" ", "") for l in lstr])', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
18.101892948150635

如果你在一个单词的字符串中有空格,那么它是无效的。例如:['hello world',' ','hello',' ']。>> ['helloworld',' ','hello',' ']。你有没有其他解决方案,可以保留列表项中的空格但删除其他空格? - Reihan_amn
1
请注意,filter(None, lstr) 不会删除带有空格 ' ' 的空字符串。是的,因为那不是一个空字符串。 - AMC
救命稻草!! - Abu Shoeb

29

总结最佳答案:

1. 不去除空字符串:

也就是说,所有的空格字符串都会被保留:

slist = list(filter(None, slist))

优点:

  • 最简单的;
  • 最快的(见下面的基准测试)。

2.消除剥离后的空字符串...

2.a ... 当字符串不包含单词之间的空格时:

slist = ' '.join(slist).split()

优点:

  • 代码量小
  • 速度快 (但在处理大型数据集时,由于内存原因并非最快,与@paolo-melchiorre的结果相反)

2.b ... 当字符串单词之间有空格时怎么办?

slist = list(filter(str.strip, slist))

优点:

  • 速度最快;
  • 代码易于理解。

2018 年机器的基准测试:

## Build test-data
#
import random, string
nwords = 10000
maxlen = 30
null_ratio = 0.1
rnd = random.Random(0)                  # deterministic results
words = [' ' * rnd.randint(0, maxlen)
         if rnd.random() > (1 - null_ratio)
         else
         ''.join(random.choices(string.ascii_letters, k=rnd.randint(0, maxlen)))
         for _i in range(nwords)
        ]

## Test functions
#
def nostrip_filter(slist):
    return list(filter(None, slist))

def nostrip_comprehension(slist):
    return [s for s in slist if s]

def strip_filter(slist):
    return list(filter(str.strip, slist))

def strip_filter_map(slist): 
    return list(filter(None, map(str.strip, slist))) 

def strip_filter_comprehension(slist):  # waste memory
    return list(filter(None, [s.strip() for s in slist]))

def strip_filter_generator(slist):
    return list(filter(None, (s.strip() for s in slist)))

def strip_join_split(slist):  # words without(!) spaces
    return ' '.join(slist).split()

## Benchmarks
#
%timeit nostrip_filter(words)
142 µs ± 16.8 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%timeit nostrip_comprehension(words)
263 µs ± 19.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit strip_filter(words)
653 µs ± 37.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit strip_filter_map(words)
642 µs ± 36 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit strip_filter_comprehension(words)
693 µs ± 42.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit strip_filter_generator(words)
750 µs ± 28.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit strip_join_split(words)
796 µs ± 103 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

s and s.strip() 可以简化为 s.strip() - AMC
如果我们想完全复制filter(None, words),则需要使用s and s.strip()。我已经更正了上面的两个示例函数,并删除了两个错误的函数。 - ankostis

24

@Ib33X的回复很棒。如果你想删除所有空字符串,剥离后需要使用strip方法。否则,如果有空格,它也会返回空字符串。例如," "对于该答案也是有效的。所以可以通过以下方式实现。

strings = ["first", "", "second ", " "]
[x.strip() for x in strings if x.strip()]
这个答案将是["first", "second"]
如果你想使用 filter 方法,你可以这样做
list(filter(lambda item: item.strip(), strings))。这会得到相同的结果。

你能解释一下这段代码吗?通常情况下,x.strip()返回False,我们已经得到了结果,但我不理解这段代码的逻辑。 - Fuad Ak

18

我会使用 if X != '' 代替 if x,以消除空字符串。就像这样:

if X != '':

str_list = [x for x in str_list if x != '']

这将保留您列表中的None数据类型。此外,如果您的列表包含整数,并且其中之一为0,则0也将被保留。

例如,

str_list = [None, '', 0, "Hi", '', "Hello"]
[x for x in str_list if x != '']
[None, 0, "Hi", "Hello"]

2
如果你的列表具有不同的类型(除了None),那么你可能会面临更大的问题。 - Tritium21
什么类型?我已经尝试了int和其他数字类型、字符串、列表、元组、集合以及空值,都没有问题。我可以看到如果有任何不支持str方法的用户定义类型可能会出现问题。我还需要担心其他什么吗? - thiruvenkadam
1
如果你有一个 str_list = [None, '', 0, "Hi", '', "Hello"],那么这是一个设计不良的应用程序的迹象。你不应该在同一个列表中拥有多个接口(类型)和 None。 - Tritium21
3
从数据库中检索数据?在进行自动化测试时函数的参数列表? - thiruvenkadam
3
通常情况下,它们是元组。 - Tritium21
这是一个很好的解决方案,@Tritium21 元组和列表可以互换使用,除了元组是可哈希的。我搜索了这个问题来确实操作一个元组。 - nehem

15

你可以使用类似这样的东西

test_list = [i for i in test_list if i]

其中test_list是您想要删除空元素的列表。


11

根据你的列表大小,如果使用list.remove()而不是创建一个新列表可能会更有效:

l = ["1", "", "3", ""]

while True:
  try:
    l.remove("")
  except ValueError:
    break

这种方法的优点是不会创建新列表,但缺点是每次都要从开头开始搜索,尽管与上面提出的使用while '' in l不同,它只需要每个''出现时搜索一次(当然有一种方法可以保留两种方法的优点,但更加复杂)。


1
你可以通过这样做来直接编辑列表:ary[:] = [e for e in ary if e]。这种方法更加简洁,而且不会使用异常来控制流程。 - Krzysztof Karski
2
嗯,那并不是真正的“原地”——我相当确定这会创建一个新列表,并将其分配给旧列表的名称。 - Andrew Jaffe
这种方法的性能非常差,因为每次删除操作都会导致数据尾部在内存中重新排序。最好一次性全部删除。 - wim

10
根据 Aziz Alto 的报告,filter(None, lstr) 没有删除带有空格' '的空字符串,但如果您确定lstr仅包含字符串,则可以使用filter(str.strip, lstr)
>>> lstr = ['hello', '', ' ', 'world', ' ']
>>> lstr
['hello', '', ' ', 'world', ' ']
>>> ' '.join(lstr).split()
['hello', 'world']
>>> filter(str.strip, lstr)
['hello', 'world']

比较我的电脑上的时间

>>> from timeit import timeit
>>> timeit('" ".join(lstr).split()', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
3.356455087661743
>>> timeit('filter(str.strip, lstr)', "lstr=['hello', '', ' ', 'world', ' ']", number=10000000)
5.276503801345825

最快的解决方法是使用' '.join(lstr).split()函数来移除''和空字符串' '。请注意,如果您的字符串包含空格,则情况会有所不同。

>>> lstr = ['hello', '', ' ', 'world', '    ', 'see you']
>>> lstr
['hello', '', ' ', 'world', '    ', 'see you']
>>> ' '.join(lstr).split()
['hello', 'world', 'see', 'you']
>>> filter(str.strip, lstr)
['hello', 'world', 'see you']
您可以看到 filter(str.strip, lstr) 保留带有空格的字符串,但是' '.join(lstr).split()将拆分这些字符串。

1
这仅适用于您的字符串不包含空格的情况。否则,您也会将这些字符串拆分。 - phillyslick
2
@BenPolinsky,正如您所报告的那样,join解决方案将使用空格拆分字符串,但过滤器不会。感谢您的评论,我改进了我的答案。 - Paolo Melchiorre

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