从Python迭代器获取最后一项的最干净方法


110

从Python 2.6的迭代器中获取最后一项的最佳方法是什么?例如说

my_iter = iter(range(5))

什么是获得的最短码/干净的方式4my_iter

我可以做到这一点,但效率似乎并不高:

[x for x in my_iter][-1]

4
迭代器假定您要遍历元素,而不是真正访问最后一个元素。是什么使您无法简单地使用range(5)[-1]?
Frank

7
@Frank-我认为实际的迭代器比iter(range(5))
Chris Lutz 2010年

3
@Frank:事实上,它实际上是提供迭代器的更为复杂的生成器函数。我只是编造了这个示例,以使它变得简单而清楚。
彼得

4
如果您想要迭代器的最后一项,那么您做错事的可能性很大。但是答案是,实际上没有任何更干净的方法可以遍历迭代器。这是因为迭代器没有大小,并且实际上可能永远不会结束,因此可能没有最后一项。(当然,意味着您的代码将永远运行)。因此,挥之不去的问题是:为什么要迭代器的最后一项?
Lennart Regebro

3
@Peter:请更新您的问题。不要在自己的问题上添加一堆评论。请更新问题并删除评论。
S.Lott

Answers:


100
item = defaultvalue
for item in my_iter:
    pass

4
为什么占位符为“默认值”?为什么不None呢?这正是None目的。您是否建议某些特定于功能的默认值甚至是正确的?如果迭代器实际上没有进行迭代,则带外值比某些误导功能的默认值更有意义。
S.Lott

46
对于我的示例,defaultvalue只是一个占位符。如果要None用作默认值,则由您选择。没有一个并不总是最明智的默认值,甚至可能没有超出范围。我个人倾向于使用“ defaultvalue = object()”来确保它是一个真正唯一的值。我只是表示默认的选择超出了本示例的范围。
Thomas Wouters,2010年

28
@ S.Lott:区分空的迭代器和具有None最终值的迭代器之间的区别也许很有用
John La Rooy 2010年

8
所有内置容器类型的所有迭代器中都有设计错误吗?第一次听说:)
Thomas Wouters 2010年

7
尽管这可能是更快的解决方案,但它依赖于for循环中的变量泄漏(某些功能(某些功能),其他程序的错误-可能是FP家伙感到震惊)。无论如何,Guido表示这将始终以这种方式工作,因此可以安全使用。
tokland 2010年

68

使用deque大小为1的。

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()

6
实际上,这是耗尽长序列的最快方法,尽管仅比for循环快。
Sven Marnach 2011年

11
+1在技术上是正确的,但是读者应该对Python有通常的警告:“您真的需要优化它吗?”,“这不太明确,不是Pythonic”和“更快的速度取决于实现,哪个可能会改变。”
leewz 2014年

1
同样,这是一个记忆中的猪
Eelco Hoogendoorn

6
@EelcoHoogendoorn为什么最大记忆体数为1时还是要消耗记忆?
克里斯·韦瑟林

1
到目前为止,从本文介绍的所有解决方案中,我发现这是最快,内存效率最高的解决方案。
Markus Strauss

66

如果您使用的是Python 3.x:

*_, last = iterator # for a better understanding check PEP 448
print(last)

如果您使用的是python 2.7:

last = next(iterator)
for last in iterator:
    continue
print last


边注:

通常情况下,上述解决方案介绍的是你需要正规的情况下什么,但如果你正在处理数据的数量较大,这是更有效地使用一个deque大小1(来源

from collections import deque

#aa is an interator
aa = iter('apple')

dd = deque(aa, maxlen=1)
last_element = dd.pop()

1
@virtualxtc:下划线只是一个标识符。前面的星星说“扩大名单”。更具可读性*lst, last = some_iterable
pepr

4
@virtualxtc nope _是python中的特殊变量,用于存储最后一个值,或者说我不在乎该值,因此可以清除。
DhiaTN

1
Python 3解决方案不是高效的内存。
Markus Strauss

3
@DhiaTN是的,您绝对正确。实际上,我喜欢您展示了很多的Python 3习惯用法。我只是想说明一下,它不适用于“大数据”。我为此使用collections.deque,它恰好快速且具有内存效率(请参阅martin23487234的解决方案)。
Markus Strauss

1
这个py3.5 +示例应该在PEP 448中。
EliadL

33

__reversed__如果可用,可能值得使用

if hasattr(my_iter,'__reversed__'):
    last = next(reversed(my_iter))
else:
    for last in my_iter:
        pass

27

简单如:

max(enumerate(the_iter))[1]

8
哦,这很聪明。不是最有效或最易读的,但很聪明。
timgeb

6
因此,只需大声思考...之所以enumerate可行,(index, value)是因为返回如下:(0, val0), (1, val1), (2, val2)...然后默认情况下,max在给定元组列表时,仅与元组的第一个值进行比较,除非两个第一个值相等,否则它们永远不在此处因为它们代表索引。那么尾随下标是因为max返回整个(idx,值)元组,而我们仅对感兴趣value。有趣的主意。
泰勒·埃德米斯顿

21

由于存在lambda,这不太可能比空的for循环快,但也许会给别人一个思路

reduce(lambda x,y:y,my_iter)

如果iter为空,则引发TypeError


恕我直言,从概念上讲,这是最直接的。除了增加TypeError一个空的可迭代对象,您还可以通过初始值提供默认值reduce(),例如last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default)
egnha

9

有这个

list( the_iter )[-1]

如果迭代的长度确实是史诗般的-如此之长以至于实现列表将耗尽内存-那么您确实需要重新考虑设计。


1
这是最直接的解决方案。
laike9m 2014年

2
使用元组要好一些。
克里斯托弗·史密斯

9
强烈不同意最后一句话。使用非常大的数据集(如果一次全部加载可能会超出内存范围)是使用迭代器而不是列表的主要原因。
保罗

@Paul:某些函数仅返回迭代器。在这种情况下(对于非史诗般的列表),这是一种简短易读的方法。
serv-inc

那是应该避免的坏习惯和坏习惯的最无效的方法。另一种方法是使用sort(sequence)[-1]获得序列的max元素。如果您想成为软件工程师,请不要使用这些不适的方式。
Maksym Ganenko,

5

我会用 reversed,只是它只序列而不是迭代器,这似乎相当武断。

无论采用哪种方式,都必须遍历整个迭代器。以最高的效率,如果您不再需要迭代器,则可以废弃所有值:

for last in my_iter:
    pass
# last is now the last item

我认为这是次佳的解决方案。


4
reversed()不需要迭代器,只需序列即可。
Thomas Wouters,2010年

3
这一点也不随意。反转迭代器的唯一方法是迭代到最后,同时将所有项目保留在内存中。我,例如,您需要先对其进行排序,然后才能将其反转。当然,这首先会破坏迭代器的目的,也意味着您会突然无故浪费大量内存。因此,实际上,这与任意性相反。:)
Lennart Regebro

@Lennart-当我说随意时,我的意思是讨厌。我要在上午的几个小时内将我的语言技能集中在我的论文上。
克里斯·卢兹

3
很公平。尽管IMO接受迭代器会更令人讨厌,因为几乎任何使用迭代器都是一个坏主意(tm)。:)
Lennart Regebro

3

图尔茨库提供了一个很好的解决方案:

from toolz.itertoolz import last
last(values)

但是,仅在这种情况下,添加非核心依赖项可能并不值得。



0

我只会用 next(reversed(myiter))


8
TypeError:reversed()的参数必须是一个序列
Labo

0

问题是关于获取迭代器的最后一个元素,但是如果您的迭代器是通过将条件应用于序列来创建的,则可以通过应用反向来查找反向序列的“第一个”,而只需查看所需的元素即可。与序列本身相反。

一个人为的例子

>>> seq = list(range(10))
>>> last_even = next(_ for _ in reversed(seq) if _ % 2 == 0)
>>> last_even
8

0

另外,对于无限迭代器,您可以使用:

from itertools import islice 
last = list(islice(iterator(), 1000))[-1] # where 1000 is number of samples 

我以为那会慢一点,deque但是它和循环方法一样快,而且实际上快得多(某种程度上)


-6

这个问题是错误的,只能导致复杂而低效的答案。要获得迭代器,您当然要从可迭代的事物开始,这在大多数情况下将提供访问最后一个元素的更直接的方法。

从可迭代对象创建迭代器后,您就不得不遍历元素,因为这是可迭代对象唯一提供的内容。

因此,最有效,最清晰的方法不是首先创建迭代器,而是使用迭代器的本机访问方法。


5
那么,如何获得文件的最后一行呢?
布莱斯·登普西

@ BriceM.Dempsey最好的方法不是遍历整个(也许是巨大的)文件,而是转到文件大小减去100,读取最后100个字节,扫描其中的换行符(如果没有),转到后退100字节等。您还可以根据情况选择增加后退大小。读取大量的行绝对是非最佳解决方案。
Alfe
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.