从列表中删除末尾的空元素

14

有没有一种优雅的pythonic方法可以从列表中删除末尾的空元素?一种类似于list.rstrip(None)的方式。

[1, 2, 3, None, 4, None, None]

应该导致

[1, 2, 3, None, 4]

我猜这可以泛化为删除任何特定值的尾随元素。

如果可能的话,我希望将其作为单行(可读)表达式完成。

12个回答

22

如果您想排除只有 None 值并保留零或其他假值,可以执行以下操作:

while my_list and my_list[-1] is None:
    my_list.pop()

要删除所有假值(零、空字符串、空列表等),可以执行以下操作:

my_list = [1, 2, 3, None, 4, None, None]
while not my_list[-1]:
    my_list.pop()
print(my_list)
# [1, 2, 3, None, 4]

1
第二段代码对于包含许多伪值的列表会失败,包括 [None], [None, None], [0, '', []] 等。另一个答案 解决了这个问题。 - Georgy

9
以下代码显式地检查了None元素:
while l and l[-1] is None:
    l.pop()

它可以概括为以下几点:
f = lambda x: x is None
while l and f(l[-1]):
    l.pop()

现在您可以定义不同的功能来检查其他条件。


5
def remove_trailing(l, remove_value=None):
    i = len(l)
    while i > 0 and l[i - 1] == remove_value:
        i -= 1
    return l[:i]

1
这是目前为止最好的答案。另一种变化是使用原地切片赋值,而不是返回切片以保留命令/查询分离。如果你喜欢的话,这是一种不错的函数式风格。 - aaronasterling
@aaronasterling 我也喜欢这个解决方案。我正在尝试学习更多关于函数式编程的知识。你所说的 原地切片赋值 是什么意思?只是将切片重新分配给 l 参数吗?而不是返回它?(肯定不是那样) - alan
@alan,不要使用return l[:i],而是使用l[:] = l[:i]。这样做会将其变成一个命令,因为它会改变l的状态。我之前的想法是错误的,因为它目前是一个查询(返回数据且不改变状态),所以它确实保留了命令查询分离。我原本想像这样使用它:l = remove_trailing(l),但那样会违反CQ分离,我认为这样做有点过于拘泥小节了。 - aaronasterling
一个很好的答案(+1),尤其是因为它是通用的,可以删除要删除的内容(在我的情况下,是文本列表末尾的空字符串)。 - Mawg says reinstate Monica

4

试试这个

>>> x=[1, 2, 3, None, 4, None, None]
>>> while x[-1] is None:
    x.pop()

你的回答与Alan的回答相似,你可以给他的回答投票,而不是发布一个新的回答。 - pylover
3
@pylover:Alan的回答创建了不必要的副本。他应该使用pop而不是它。 - Abhijit

1

一行代码解决方案:

In [30]: from itertools import dropwhile

In [31]: list(reversed(tuple(dropwhile(lambda x: x is None, reversed([1, 2, 3, None, 4, None, None])))))
Out[31]: [1, 2, 3, None, 4]

如果你想重复使用它,在这里是一个无点风格的定义:

In [36]: from functional import compose, partial

In [37]: varargs = lambda *args: args

In [38]: compose_mult = compose(partial(reduce, compose),varargs) # compose which takes variable number of arguments. Innermost function to the right.

In [39]: compose_mult(list, reversed, tuple, partial(dropwhile, lambda x: x is None), reversed)([1, 2, 3, None, 4, None, None])
Out[39]: [1, 2, 3, None, 4]

在我看来,它虽然能够工作,但是牺牲了太多的可读性。 - Simeon Visser
@SimeonVisser 这是个人观点问题,但是没错,我也不太喜欢在中间强制转换为元组的需要。 - Marcin

1
我从未接受过答案,因为我对提供的解决方案并不满意。这是另一种解决方案(只有一行且没有库依赖项),但我也不完全满意:
a = [1, 2, None, 3, None, None]
reduce(lambda l, e: [e]+l if l or e is not None else [], reversed(a), [])

如果列表有 https://docs.python.org/3.6/library/stdtypes.html#str.rstrip 这样的方法就好了。但是既然没有,Alan 的两行 pop 循环对我来说也管用。 - Dave

0
如果你正在寻找一行代码,它可能是这样的:
a = [1, 2, 3, None, 4, None, None]
b = [x for n, x in enumerate(a) if any(y is not None for y in a[n:])]
print b

请注意,它是二次的(在最坏情况下)。
对于任何假值,它甚至更简单:
b = [x for n, x in enumerate(a) if any(a[n:])]

0

如果你真的需要牺牲可读性,这是我会做的。

>>> from itertools import takewhile
>>> l=[1,2,3,None,4,5,None,None]
>>> l[:-len(list(takewhile(lambda x: x==None, reversed(l))))]
[1,2,3,None,4,5]

从你的输出来看,似乎这并没有实现任何东西。 - Marcin
我复制粘贴了错误的行,但它实现了正确的结果,而且比许多其他解决方案更快。 - luke14free

0

more_itertools 项目为任何可迭代对象实现了 rstrip 方法:

iterable = [1, 2, 3, None, 4, None, None]
list(mit.rstrip(iterable, lambda x: x in {None}))
# [1, 2, 3, None, 4]

more_itertools.rstrip 接受一个可迭代对象和谓词。有关详细信息,请参见源代码


-1

这个怎么样:

[a[i] for i in range(len(a)) if a[i:] != [None] * (len(a) - i) ]

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