Самый чистый способ получить последний элемент из итератора Python

Как лучше всего получить последний элемент из итератора в Python 2.6? Например, скажите

my_iter = iter(range(5))

Каков самый короткий / самый чистый способ получить 4 от my_iter?

Я мог бы это сделать, но это кажется не очень эффективным:

[x for x in my_iter][-1]

person Peter    schedule 26.01.2010    source источник
comment
Итераторы предполагают, что вы хотите перебирать элементы, а не обращаться к последним элементам. Что мешает вам просто использовать range (5) [- 1]?   -  person Frank    schedule 26.01.2010
comment
@Frank - я предположил, что реальный итератор был более сложным и / или более удаленным, и / или более сложным в управлении, чем iter(range(5))   -  person Chris Lutz    schedule 26.01.2010
comment
@Frank: тот факт, что на самом деле это гораздо более сложная функция генератора, которая снабжает итератор. Я просто придумал этот пример, чтобы было просто и понятно, что происходит.   -  person Peter    schedule 26.01.2010
comment
Если вам нужен последний элемент итератора, есть большая вероятность, что вы делаете что-то не так. Но ответ в том, что на самом деле нет более чистого способа перебора итератора. Это связано с тем, что итераторы не имеют размера и, по сути, могут вообще никогда не заканчиваться, и поэтому могут не иметь последнего элемента. (Это, конечно, означает, что ваш код будет работать вечно). Итак, остается нерешенным вопрос: зачем вам последний элемент итератора?   -  person Lennart Regebro    schedule 26.01.2010
comment
@Lennart: хорошо, я надеялся избежать этих проблем для ясности, но вот что: у меня есть функция, которая работает в двух режимах. В одном я получаю промежуточные результаты и сообщаю о них. В другом («пакетном» режиме) я не забочусь о промежуточных отчетах и ​​хочу получить только последний результат. Я могу изменить функцию, но производительность не имеет особого значения, поэтому я просто отброшу промежуточные результаты и оставлю последние.   -  person Peter    schedule 26.01.2010
comment
@Peter: Обновите свой вопрос, пожалуйста. Не добавляйте кучу комментариев к собственному вопросу. Обновите вопрос и удалите комментарии.   -  person S.Lott    schedule 26.01.2010
comment
@Peter: отказ от варианта использования не увеличивает ясности. Фактически это уменьшает его. :)   -  person Lennart Regebro    schedule 26.01.2010
comment
@Peter: Я думаю, что было дано много хороших решений. Однако, если это исходит от метода _ _ iter _ _ класса, вам следует также рассмотреть возможность реализации _ _ reversed _ _ (если возможно), что сделает ситуацию немного чище.   -  person tixxit    schedule 26.01.2010


Ответы (14)


Если вы используете 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()
person DhiaTN    schedule 12.01.2018
comment
Не могли бы вы объяснить *_ часть? - person virtualxtc; 28.06.2018
comment
@virtualxtc см. PEP 448 для получения дополнительных сведений. - person DhiaTN; 28.06.2018
comment
@virtualxtc: подчеркивание - это просто идентификатор. Звездочка впереди говорит: «Расширьте список». Более читабельным будет *lst, last = some_iterable. - person pepr; 28.06.2018
comment
@virtualxtc nope _ - это специальная переменная в python, которая используется либо для хранения последнего значения, либо для того, чтобы сказать, что меня не волнует значение, поэтому его можно очистить. - person DhiaTN; 28.06.2018
comment
Это решение Python 3 неэффективно с точки зрения памяти. - person Markus Strauss; 03.09.2018
comment
@MarkusStrauss Я имею в виду, что это зависит от размера и необходимого уровня оптимизации, для обычных случаев использования должно быть более чем достаточно, но я был бы рад узнать, как вы это делаете :) - person DhiaTN; 03.09.2018
comment
@DhiaTN Да, вы абсолютно правы. На самом деле, мне очень нравится идиома Python 3, которую вы показали. Я просто хотел прояснить, что это не работает для больших данных. Для этого я использую collections.deque, который оказывается быстрым и эффективным с точки зрения памяти (см. Решение от martin23487234). - person Markus Strauss; 06.09.2018
comment
Этот пример py3.5 + должен быть в PEP 448. Замечательно. - person EliadL; 10.01.2019
comment
*_, last = iterator будет повышаться, если iterator пусто. Будьте готовы _3 _ / _ 4_, если вы используете его для данных, которые могут быть пустыми. - person Fund Monica's Lawsuit; 17.07.2019
comment
Что в данном контексте означает NB? Не думаю, что я видел это за 30 с лишним лет в Интернете. - person Catskul; 23.01.2020
comment
Я имею в виду примечание - person DhiaTN; 24.01.2020
comment
@DhiaTN _ не является специальной переменной в Python. Это нормальный идентификатор, который действует как любой другой. Вы правы, что обычно говорят, что меня не волнует это значение, потому что оно выглядит необычно для имени переменной, но это просто соглашение; Сам Python вообще не обрабатывает его специально, в отличие от таких языков, как Go, где _ зарезервирован языком для одноразового идентификатора и не может хранить значения. (Использование Python REPL _ для хранения последнего значения также не связано с самим языком Python, это просто еще один пример соглашения) - person hallo; 01.07.2020
comment
Это важно понимать, потому что оно объясняет утверждение Маркуса Штрауса о том, что пример 3.x неэффективен с точки зрения памяти. Видите ли, если бы _ был на самом деле указанным в спецификации одноразовым идентификатором, то *_, last = some_huge_iterator() работал бы совершенно нормально, независимо от размера итератора. Одноразовые ценности будут отброшены сразу после генерации, и они никогда не увидят свет. Но поскольку _ - это просто обычный идентификатор, все, что делает этот фрагмент, - это создает большой старый список значений и сохраняет его все в переменной с именем _. Вот почему он использует много памяти для больших данных. - person hallo; 01.07.2020

Используйте deque размера 1.

from collections import deque

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

dd = deque(aa, maxlen=1)
last_element = dd.pop()
person martin23487234    schedule 02.07.2010
comment
На самом деле это самый быстрый способ исчерпать длинную последовательность, хотя и ненамного быстрее, чем цикл for. - person Sven Marnach; 22.01.2011
comment
+1 за то, что он технически правильный, но читатели должны иметь обычные предостережения Python: Вам ДЕЙСТВИТЕЛЬНО нужно оптимизировать это? Это менее явно, что не является Pythonic, и Более высокая скорость зависит от реализации, которая может измениться. - person leewz; 18.02.2014
comment
Кроме того, это боров памяти - person Eelco Hoogendoorn; 28.07.2016
comment
@EelcoHoogendoorn Почему у него слишком много памяти, даже если maxlen равен 1? - person Chris Wesseling; 15.12.2016
comment
Из всех представленных здесь решений я считаю это самым быстрым и наиболее эффективным с точки зрения памяти. - person Markus Strauss; 03.09.2018

Наверное, стоит использовать __reversed__, если он доступен

if hasattr(my_iter,'__reversed__'):
    last = next(reversed(my_iter))
else:
    for last in my_iter:
        pass
person John La Rooy    schedule 05.02.2010

Так просто как:

max(enumerate(the_iter))[1]
person Chema Cortes    schedule 06.06.2011
comment
О, это умно. Не самый эффективный или читаемый, но умный. - person timgeb; 26.03.2016
comment
Так что просто подумайте вслух ... Это работает, потому что enumerate возвращает (index, value), например: _3 _..., а затем по умолчанию max, когда задан список кортежей, сравнивается только с первым значением кортежа, если только два первых значения не равны, что их здесь нет, потому что они представляют собой индексы. Тогда конечный нижний индекс объясняется тем, что max возвращает весь кортеж (idx, value), тогда как нас интересует только value. Интересная идея. - person Taylor Edmiston; 12.06.2017

Маловероятно, что это будет быстрее, чем пустой цикл for из-за лямбда, но, возможно, это подскажет кому-то еще

reduce(lambda x,y:y,my_iter)

Если iter пуст, возникает ошибка TypeError.

person John La Rooy    schedule 26.01.2010
comment
ИМХО, эта самая прямая, концептуально. Вместо того, чтобы повышать TypeError для пустой итерации, вы также можете указать значение по умолчанию через начальное значение reduce(), например, last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default). - person egnha; 11.12.2019

Вот это

list( the_iter )[-1]

Если длина итерации действительно эпична - настолько велика, что материализация списка истощит память - тогда вам действительно нужно переосмыслить дизайн.

person S.Lott    schedule 26.01.2010
comment
Это наиболее простое решение. - person laike9m; 23.03.2014
comment
Мягко говоря, лучше использовать кортеж. - person Christopher Smith; 09.02.2015
comment
Категорически не согласен с последним предложением. Основная причина использования итератора вместо списка - работа с очень большими наборами данных (которые могут превышать границы памяти при загрузке всех сразу). - person Paul; 07.04.2016
comment
@Paul: некоторые функции возвращают только итератор. Это короткий и довольно читаемый способ сделать это в этом случае (для неэпических списков). - person serv-inc; 14.11.2017
comment
Это наименее эффективный способ избежать вредной вредной привычки. Другой - использовать sort (sequence) [- 1] для получения максимального элемента последовательности. Пожалуйста, никогда не используйте эти дурные паттерны, если вам нравится работать инженером-программистом. - person Maksym Ganenko; 28.08.2019
comment
Это в значительной степени то, что ОП сказал, что он не хотел делать. - person Jim; 29.09.2020

Я бы использовал reversed, за исключением того, что он принимает только последовательности вместо итераторов, что кажется довольно произвольным.

Как бы то ни было, вам придется пройти через весь итератор. При максимальной эффективности, если итератор больше не понадобится, можно просто выбросить все значения:

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

Я думаю, что это неоптимальное решение.

person Chris Lutz    schedule 26.01.2010
comment
reversed () не использует итератор, только последовательности. - person Thomas Wouters; 26.01.2010
comment
Это вовсе не произвольно. Единственный способ отменить итератор - выполнить итерацию до конца, сохраняя при этом все элементы в памяти. Я, е, вам нужно сначала сделать из этого последовательность, прежде чем вы сможете ее отменить. Что, конечно, в первую очередь сводит на нет цель итератора, а также означает, что вы внезапно израсходовали много памяти без видимой причины. На самом деле это противоположность произволу. :) - person Lennart Regebro; 26.01.2010
comment
@Lennart - Когда я сказал произвольно, я имел в виду раздражающий. Я сосредотачиваю свои языковые навыки на работе, которую нужно сдать через несколько часов, в это время утром. - person Chris Lutz; 26.01.2010
comment
Справедливо. Хотя ИМО было бы более раздражающим, если бы он действительно принимал итераторы, потому что почти любое его использование было бы плохой идеей (tm). :) - person Lennart Regebro; 26.01.2010

Библиотека toolz предоставляет хорошее решение:

from toolz.itertoolz import last
last(values)

Но добавление неосновной зависимости может не стоить того, чтобы использовать ее только в этом случае.

person lumbric    schedule 27.11.2018

См. Этот код для чего-то похожего:

http://excamera.com/sphinx/article-islast.html

вы можете использовать его, чтобы забрать последний предмет:

[(last, e) for (last, e) in islast(the_iter) if last]
person James Bowman    schedule 12.01.2011
comment
Включите код для islast в свой ответ (см. meta.stackexchange.com/questions/8231/). - person Cristian Ciupitu; 23.04.2015

Я бы просто использовал next(reversed(myiter))

person thomas.mac    schedule 24.10.2017
comment
TypeError: аргумент для reversed () должен быть последовательностью - person Labo; 05.01.2018

Вопрос в том, чтобы получить последний элемент итератора, но если ваш итератор создается путем применения условий к последовательности, то обратное можно использовать для поиска «первого» в обратной последовательности, только глядя на необходимые элементы, применяя обратный к самой последовательности.

Надуманный пример,

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

В качестве альтернативы для бесконечных итераторов вы можете использовать:

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

Я думал, что это будет медленнее, чем deque, но это так же быстро и на самом деле быстрее, чем для метода цикла (как-то)

person qocu    schedule 31.05.2019

Вопрос неправильный и может привести только к сложному и неэффективному ответу. Чтобы получить итератор, вы, конечно же, начинаете с чего-то повторяемого, что в большинстве случаев предлагает более прямой способ доступа к последнему элементу.

Как только вы создаете итератор из итерируемого объекта, вы застреваете в прохождении элементов, потому что это единственное, что предоставляет итератор.

Итак, наиболее эффективный и понятный способ - это не создавать итератор в первую очередь, а использовать собственные методы доступа к итератору.

person ludwig    schedule 29.06.2010
comment
Так как же получить последнюю строку файла? - person Brice M. Dempsey; 25.07.2015
comment
@ BriceM.Dempsey Лучше всего не перебирать весь (возможно, огромный) файл, а перейти к размеру файла минус 100, прочитать последние 100 байтов, найти в них новую строку, если ее нет, перейдите назад еще на 100 байт и т. д. Вы также можете увеличить размер шага назад, в зависимости от вашего сценария. Определенно, читать миллиард строк - неоптимальное решение. - person Alfe; 04.06.2019

person    schedule
comment
Почему используется значение по умолчанию для заполнителя? Почему не None? Это именно то, для чего нужен None. Вы предполагаете, что какое-то значение по умолчанию для конкретной функции может быть правильным? Если итератор на самом деле не выполняет итерацию, то значение вне диапазона более значимо, чем какое-либо вводящее в заблуждение значение по умолчанию для конкретной функции. - person S.Lott; 26.01.2010
comment
Значение по умолчанию - это просто заполнитель для моего примера. Если вы хотите использовать None в качестве значения по умолчанию, это ваш выбор. «Нет» не всегда является наиболее разумным значением по умолчанию и может даже не выходить за рамки допустимого. Лично я предпочитаю использовать defaultvalue = object (), чтобы убедиться, что это действительно уникальное значение. Я просто указываю, что выбор значения по умолчанию выходит за рамки этого примера. - person Thomas Wouters; 26.01.2010
comment
@ S.Lott: возможно, полезно различать пустой итератор и итератор, имеющий None в качестве конечного значения - person John La Rooy; 26.01.2010
comment
Если ваш итератор может иметь None в качестве допустимого значения, вы, вероятно, неправильно его спроектировали. Исключение могло иметь больше смысла, чем None. Особенно, когда этот вариант использования существует как часть дизайна. - person S.Lott; 26.01.2010
comment
Есть ошибка дизайна во всех итераторах всех типов встроенных контейнеров? Впервые слышу об этом :) - person Thomas Wouters; 26.01.2010
comment
Хотя это, вероятно, более быстрое решение, оно полагается на утечку переменной в циклах for (функция для некоторых, ошибка для других - вероятно, FP-ребята в ужасе). Во всяком случае, Гвидо сказал, что так будет всегда, так что это безопасная конструкция в использовании. - person tokland; 23.07.2010
comment
Это более быстрое решение, но не самое быстрое. Самый быстрый - это выполнить цикл for в C, который выполняется с использованием collections.deque в соответствии с поздним ответом martin23487234. Подобно тому, как работает consume в рецептах itertools. - person Muhammad Alkarouri; 15.09.2010
comment
item не нужно определять вначале; вместо использования item = defaultvalue я предлагаю просто итерацию и захват NameError для случая пустого итератора - person Chris_Rands; 29.05.2017
comment
Захват NameError - не лучшая идея, поскольку вы не можете быть уверены, какой NameError вы поймаете. Вы можете скрыть ошибки в самом итераторе. - person Thomas Wouters; 12.06.2017