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

110

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

my_iter = iter(range(5))

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

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

[x for x in my_iter][-1]
Питер
источник
4
Итераторы предполагают, что вы хотите перебирать элементы, а не обращаться к последним элементам. Что мешает вам просто использовать range (5) [- 1]?
Фрэнк
7
@Frank - я предположил, что реальный итератор был более сложным и / или более удаленным, и / или более сложным для управления, чемiter(range(5))
Крис Лутц
3
@Frank: тот факт, что на самом деле это гораздо более сложная функция генератора, которая снабжает итератор. Я просто придумал этот пример, чтобы было просто и понятно, что происходит.
Питер
4
Если вам нужен последний элемент итератора, есть большая вероятность, что вы делаете что-то не так. Но ответ в том, что на самом деле нет более чистого способа перебора итератора. Это связано с тем, что итераторы не имеют размера и, по сути, могут вообще никогда не заканчиваться, и поэтому могут не иметь последнего элемента. (Это, конечно, означает, что ваш код будет работать вечно). Итак, остается нерешенным вопрос: зачем вам нужен последний элемент итератора?
Леннарт Регебро
3
@Peter: Обновите свой вопрос, пожалуйста. Не добавляйте кучу комментариев к собственному вопросу. Обновите вопрос и удалите комментарии.
S.Lott

Ответы:

100
item = defaultvalue
for item in my_iter:
    pass
Томас Воутерс
источник
4
Почему заполнитель «значение по умолчанию»? А почему бы и нет None? Именно для этого и нужно None. Вы предполагаете, что какое-то значение по умолчанию для конкретной функции может быть правильным? Если итератор на самом деле не выполняет итерацию, то значение вне диапазона более значимо, чем какое-то вводящее в заблуждение значение по умолчанию для конкретной функции.
S.Lott
46
Значение по умолчанию - это просто заполнитель для моего примера. Если вы хотите использовать Noneзначение по умолчанию, это ваш выбор. Ни один не всегда является наиболее разумным значением по умолчанию и может даже не выходить за рамки допустимого. Лично я предпочитаю использовать defaultvalue = object (), чтобы убедиться, что это действительно уникальное значение. Я просто указываю, что выбор значения по умолчанию выходит за рамки этого примера.
Thomas Wouters
28
@ S.Lott: возможно, полезно различать пустой итератор и итератор, имеющий Noneокончательное значение
Джон Ла Рой,
8
Есть ошибка дизайна во всех итераторах всех типов встроенных контейнеров? Впервые слышу об этом :)
Томас Воутерс
7
Хотя это, вероятно, более быстрое решение, оно полагается на утечку переменной в циклах for (функция для некоторых, ошибка для других - вероятно, FP-ребята в ужасе). Во всяком случае, Гвидо сказал, что так будет всегда, так что это безопасная конструкция в использовании.
tokland
68

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

from collections import deque

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

dd = deque(aa, maxlen=1)
last_element = dd.pop()
martin23487234
источник
6
На самом деле это самый быстрый способ исчерпать длинную последовательность, хотя и ненамного быстрее, чем цикл for.
Sven Marnach
11
+1 за техническую правильность, но читатели должны иметь обычные предостережения Python: «Вам ДЕЙСТВИТЕЛЬНО нужно это оптимизировать?», «Это менее явно, что не является Pythonic» и «Более высокая скорость зависит от реализации, которая может поменяться."
leewz
1
Кроме того, это боров памяти
Eelco Hoogendoorn
6
@EelcoHoogendoorn Почему у него слишком много памяти, даже если maxlen равен 1?
Крис Весселинг,
1
Из всех представленных здесь решений я считаю это самым быстрым и эффективным с точки зрения памяти .
Маркус Штраус
67

Если вы используете 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()
DhiaTN
источник
1
@virtualxtc: подчеркивание - это просто идентификатор. Звезда впереди говорит: «Расширьте список». Более читабельным было бы *lst, last = some_iterable.
pepr
4
@virtualxtc nope _- это специальная переменная в python, которая используется либо для хранения последнего значения, либо для того, чтобы сказать, что меня не волнует значение, поэтому его можно очистить.
DhiaTN
1
Это решение Python 3 неэффективно с точки зрения памяти.
Маркус Штраус
3
@DhiaTN Да, вы абсолютно правы. На самом деле, мне очень нравится идиома Python 3, которую вы показали. Я просто хотел прояснить, что это не работает для «больших данных». Для этого я использую collections.deque, который оказывается быстрым и эффективным с точки зрения памяти (см. Решение от martin23487234).
Маркус Штраус
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), а нас интересует только value. Интересная идея.
Taylor
21

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

reduce(lambda x,y:y,my_iter)

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

Джон Ла Рой
источник
ИМХО, этот самый прямой, концептуально. Вместо того, чтобы поднимать TypeErrorна пустой Iterable, вы можете также указать значение по умолчанию с помощью начального значения reduce(), например, last = lambda iterable, default=None: reduce(lambda _, x: x, iterable, default).
egnha
9

Вот это

list( the_iter )[-1]

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

С. Лотт
источник
1
Это наиболее простое решение.
laike9m
2
Мягко говоря, лучше использовать кортеж.
Кристофер Смит
9
Категорически не согласен с последним предложением. Основная причина использования итератора вместо списка - работа с очень большими наборами данных (которые могут выйти за пределы памяти, если загружены все сразу).
Пол
@Paul: некоторые функции возвращают только итератор. Это короткий и довольно читаемый способ сделать это в этом случае (для неэпических списков).
serv-inc
Это наименее эффективный способ избежать вредной вредной привычки. Другой - использовать sort (sequence) [- 1], чтобы получить максимальный элемент последовательности. Пожалуйста, никогда не используйте эти дурные паттерны, если вы любите работать программистом.
Максим Ганенко
5

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

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

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

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

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

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

from toolz.itertoolz import last
last(values)

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

поясничный
источник
0

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

thomas.mac
источник
8
TypeError: аргумент для reversed () должен быть последовательностью
Labo
0

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

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

>>> seq = list(range(10))
>>> last_even = next(_ for _ in reversed(seq) if _ % 2 == 0)
>>> last_even
8
Wyrmwood
источник
0

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

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

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

qocu
источник
-6

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

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

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

Ludwig
источник
5
Так как же получить последнюю строку файла?
Брайс М. Демпси
@ BriceM.Dempsey Лучше всего не перебирать весь (возможно, огромный) файл, а перейти к размеру файла минус 100, прочитать последние 100 байт, найти в них новую строку, если ее нет, перейдите назад еще на 100 байт и т. д. Вы также можете увеличить размер шага назад, в зависимости от вашего сценария. Определенно, читать миллиард строк - неоптимальное решение.
Alfe