Який пітонічний спосіб виявити останній елемент у циклі 'for'?


188

Я хотів би знати найкращий спосіб (більш компактний і «пітонічний») зробити спеціальну обробку для останнього елемента в циклі for. Є фрагмент коду, який слід викликати лише між елементами, придушуючись в останньому.

Ось як я зараз це роблю:

for i, data in enumerate(data_list):
    code_that_is_done_for_every_element
    if i != len(data_list) - 1:
        code_that_is_done_between_elements

Чи є кращий спосіб?

Примітка. Я не хочу робити це з такими хаками, як використання reduce.;)


А що він перший? Чи слід його також придушувати?
Адам Матан

ви можете сказати нам, що це робиться між елементами?
SilentGhost

2
Я хотів би отримати відповідь на загальний випадок, але конкретний випадок, коли мені це потрібно, - це написання речей на потоці, з роздільниками між ними, як stream.write (',' .join (name_list)), але робити це в циклі for, не з'єднуючи рядки, тому що існує багато записів ...
e.tadeu


Перші три рядки цієї відповіді мені справді допомогли, мав подібний виклик.
кардамон

Відповіді:


151

У більшості випадків простіше (і дешевше) зробити першу ітерацію окремим випадком замість останнього:

first = True
for data in data_list:
    if first:
        first = False
    else:
        between_items()

    item()

Це підійде для будь-яких ітерабельних, навіть для тих, у кого немає len():

file = open('/path/to/file')
for line in file:
    process_line(line)

    # No way of telling if this is the last line!

Крім того, я не думаю, що в цілому є найкраще рішення, оскільки це залежить від того, що ви намагаєтеся зробити. Наприклад, якщо ви будуєте рядок зі списку, це, природно, краще використовувати, str.join()ніж використовувати forцикл "зі спеціальним регістром".


Використовуючи той же принцип, але більш компактний:

for i, line in enumerate(data_list):
    if i > 0:
        between_items()
    item()

Виглядає знайомо, чи не так? :)


Для @ofko та інших, кому дійсно потрібно з’ясувати, чи є поточне значення без ітерабету без len() останнього, вам потрібно буде заглянути вперед:

def lookahead(iterable):
    """Pass through all values from the given iterable, augmented by the
    information if there are more values to come after the current one
    (True), or if it is the last value (False).
    """
    # Get an iterator and pull the first value.
    it = iter(iterable)
    last = next(it)
    # Run the iterator to exhaustion (starting from the second value).
    for val in it:
        # Report the *previous* value (more to come).
        yield last, True
        last = val
    # Report the last value.
    yield last, False

Тоді ви можете використовувати його так:

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False

1
Щоправда, цей спосіб здається кращим, ніж мій, принаймні для цього не потрібно використовувати перерахування та подовження.
e.tadeu

Так, але додається ще одна, ifякої можна було б уникнути, якби петля була розділена на дві петлі. Однак це актуально лише при ітерації величезного списку даних.
Адам Матан

Проблема розбиття на дві петлі полягає в тому, що вона або порушує DRY, або змушує вас визначити методи.
e.tadeu

Я дійсно намагаюся зрозуміти ваш останній приклад (який працює бездоганно в моєму коді), але я не розумію, як це працює (ідея)
Олів'є Понс

1
@OlivierPons Вам потрібно зрозуміти протокол ітератора Python: я отримую ітератор для об'єкта та отримую перше значення за допомогою next(). Потім я використовую, що ітератор є ітерабельним сам по собі, тому можу використовувати його у forциклі до вичерпання, ітерації від другого до останнього значення. Під час цього я зберігаю поточне значення, яке я отримав з ітератора локально, і yieldостаннє замість цього. Таким чином я знаю, що має бути ще одна цінність. Після циклу for я повідомив про кожне значення, окрім останнього.
Фердинанд Бейєр

20

Хоча це питання досить старе, я прийшов сюди через google, і я знайшов досить простий спосіб: перерізати список. Скажімо, ви хочете поставити "&" між усіма записами списку.

s = ""
l = [1, 2, 3]
for i in l[:-1]:
    s = s + str(i) + ' & '
s = s + str(l[-1])

Це повертає "1 & 2 & 3".


7
Ви щойно повторили функцію приєднання: `" & ".join ([str (x) for x in l])
Bryan Oakley

конкатенація рядків дещо неефективна. Якщо len(l)=1000000в цьому прикладі програма буде працювати деякий час. appendрекомендується афаїк. l=[1,2,3]; l.append(4);
plhn

18

"Код між" - це приклад головного хвоста шаблону .

У вас є елемент, за яким слідує послідовність (між, пунктом) пар. Ви також можете розглядати це як послідовність пар (елемент, між), за якими слідує елемент. Зазвичай простіше сприймати перший елемент як особливий, а всі інші як "стандартний" випадок.

Крім того, щоб уникнути повторення коду, ви повинні надати функцію або інший об'єкт, який містить код, який ви не хочете повторювати. Вставлення оператора if у циклі, який завжди є помилковим, окрім одного разу, є начебто нерозумним.

def item_processing( item ):
    # *the common processing*

head_tail_iter = iter( someSequence )
head = head_tail_iter.next()
item_processing( head )
for item in head_tail_iter:
    # *the between processing*
    item_processing( item )

Це більш надійно, тому що це трохи легше довести, він не створює додаткової структури даних (тобто копії списку) і не вимагає багато марного виконання умови if, яка завжди помилкова, крім одного разу.


4
Виклики функцій набагато повільніше, ніж ifоператори, тому аргумент "витрачене виконання" не має значення.
Фердинанд Бейєр

1
Я не впевнений, яка різниця швидкості між викликом функції та оператором if-statement пов'язана ні з чим. Справа в тому, що в цій формулярі немає твердження if, яке завжди хибне (крім одного разу)
S.Lott

1
Я інтерпретував ваше твердження "... і не вимагає багато марного виконання умови if, яка завжди хибна, крім одного разу", як "... і швидше, оскільки це економить пару ifс". Очевидно, ви просто замислюєтесь над «чистотою коду»?
Фердинанд Бейер

Чи визначення функції замість використання ifоператора дійсно вважається більш чистим Python?
Маркус фон

17

Якщо ви просто хочете змінити останній елемент, data_listтоді ви можете просто позначити:

L[-1]

Однак, схоже, ти робиш більше, ніж це. Нічого насправді не так з вашим способом. Я навіть швидко поглянув на деякий код Django для їх тегів шаблонів, і вони в основному роблять те, що ви робите.


1
Я не змінюю його, я використовую його, щоб зробити щось
e.tadeu

4
@ e.tadeu це навіть не має значення, змінюєте ви його чи ні. Зміна вашої заяви if на: if data != datalist[-1]:а збереження всього іншого однаково було б найкращим способом кодувати це на мою думку.
spacetyper

8
@spacetyper Це порушується, коли останнє значення не унікальне.
Арк-кун

14

якщо елементи унікальні:

for x in list:
    #code
    if x == list[-1]:
        #code

інші варіанти:

pos = -1
for x in list:
    pos += 1
    #code
    if pos == len(list) - 1:
        #code


for x in list:
    #code
#code - e.g. print x


if len(list) > 0:
    for x in list[:-1]
        #code
    for x in list[-1]:
        #code

10

Це схоже на підхід Ants Aasma, але без використання модуля itertools. Це також відстаючи ітератор, який випереджає один елемент у потоці ітератора:

def last_iter(it):
    # Ensure it's an iterator and get the first field
    it = iter(it)
    prev = next(it)
    for item in it:
        # Lag by one item so I know I'm not at the end
        yield 0, prev
        prev = item
    # Last item
    yield 1, prev

def test(data):
    result = list(last_iter(data))
    if not result:
        return
    if len(result) > 1:
        assert set(x[0] for x in result[:-1]) == set([0]), result
    assert result[-1][0] == 1

test([])
test([1])
test([1, 2])
test(range(5))
test(xrange(4))

for is_last, item in last_iter("Hi!"):
    print is_last, item

4

Ви можете скористатися розсувним вікном над вхідними даними, щоб отримати вигляд наступного значення та використовувати дозорну для виявлення останнього значення. Це працює на будь-якому ітерабельному, тому вам не потрібно заздалегідь знати довжину. Парна реалізація - з рецептів itertools .

from itertools import tee, izip, chain

def pairwise(seq):
    a,b = tee(seq)
    next(b, None)
    return izip(a,b)

def annotated_last(seq):
    """Returns an iterable of pairs of input item and a boolean that show if
    the current item is the last item in the sequence."""
    MISSING = object()
    for current_item, next_item in pairwise(chain(seq, [MISSING])):
        yield current_item, next_item is MISSING:

for item, is_last_item in annotated_last(data_list):
    if is_last_item:
        # current item is the last item

3

Чи не існує можливості перебирати весь, але останній елемент, і обробляти останній поза циклом? Зрештою, цикл створений для того, щоб зробити щось подібне до всіх елементів, над якими ви перебуваєте; якщо одному елементу потрібно щось особливе, він не повинен знаходитися в циклі.

(див. також це питання: " чи-останній елемент-в-циклі-заслуговує-на-окреме лікування" )

EDIT: оскільки питання стосується більше "між", або перший елемент є спеціальним тим, що у нього немає попередника, або останній елемент особливий тим, що не має наступника.


Але до останнього елемента слід ставитися аналогічно до кожного іншого елемента у списку. Проблема полягає в тому, що слід робити лише між елементами.
e.tadeu

У цьому випадку перший - єдиний без попередника. Візьміть його один від одного і переведіть петлю на решту загального коду списку.
xtofl

3

Мені подобається підхід @ ethan-t, але while Trueвін небезпечний з моєї точки зору.

data_list = [1, 2, 3, 2, 1]  # sample data
L = list(data_list)  # destroy L instead of data_list
while L:
    e = L.pop(0)
    if L:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')
del L

Ось data_listтак, що останній елемент за значенням дорівнює першому в списку. L можна обміняти, data_listале в цьому випадку це результат порожній після циклу. while Trueтакож можна використовувати, якщо перед обробкою ви перевіряєте, що список не порожній або чек не потрібен (ой!).

data_list = [1, 2, 3, 2, 1]
if data_list:
    while True:
        e = data_list.pop(0)
        if data_list:
            print(f'process element {e}')
        else:
            print(f'process last element {e}')
            break
else:
    print('list is empty')

Гарна частина полягає в тому, що це швидко. Поганий - він руйнівний (data_list стає порожнім).

Найінтуїтивніше рішення:

data_list = [1, 2, 3, 2, 1]  # sample data
for i, e in enumerate(data_list):
    if i != len(data_list) - 1:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')

О так, ви вже запропонували це!


2

У вашому шляху немає нічого поганого, якщо ви не матимете 100 000 циклів і не хочете зекономити 100 000 операторів "if". У такому випадку ви можете піти таким чином:

iterable = [1,2,3] # Your date
iterator = iter(iterable) # get the data iterator

try :   # wrap all in a try / except
    while 1 : 
        item = iterator.next() 
        print item # put the "for loop" code here
except StopIteration, e : # make the process on the last element here
    print item

Виходи:

1
2
3
3

Але дійсно, у вашому випадку я відчуваю, що це надмірно.

У будь-якому випадку вам, мабуть, пощастить з нарізкою:

for item in iterable[:-1] :
    print item
print "last :", iterable[-1]

#outputs
1
2
last : 3

або просто:

for item in iterable :
    print item
print iterable[-1]

#outputs
1
2
3
last : 3

Врешті-решт, KISS спосіб робити ваші речі, і це може працювати з будь-яким ітерабельним, у тому числі без таких __len__:

item = ''
for item in iterable :
    print item
print item

Виходи:

1
2
3
3

Якщо мені здається, що я б це зробив саме так, мені здається простим.


2
Але зверніть увагу , що ітерацію [-1] не працюватиме на всіх ітеріруемимі (наприклад, генератора , які не мають LEN )
e.tadeu

Якщо ви хочете лише отримати доступ до останнього елемента після циклу, просто використовуйте itemзамість того, щоб повторно обчислювати його, використовуючи list[-1]. Але все-таки: я не думаю, що саме про це просила ОП, чи не так?
Фердинанд Бейер

Re: iterable.__iter__() - Будь ласка, не телефонуйте __безпосередньо функціям. Повинно бути iter(iterable).
PaulMcG

2

Використовуйте нарізку та isперевірте наявність останнього елемента:

for data in data_list:
    <code_that_is_done_for_every_element>
    if not data is data_list[-1]:
        <code_that_is_done_between_elements>

Caveat emptor : Це працює лише в тому випадку, якщо всі елементи в списку насправді різні (у пам'яті є різні місця). Під кришкою Python може виявити рівні елементи та повторно використовувати для них однакові об’єкти. Наприклад, для рядків однакового значення та загальних цілих чисел.


2

якщо ви переглядаєте список, для мене це також працювало:

for j in range(0, len(Array)):
    if len(Array) - j > 1:
        notLast()

2

Google підвів мене до цього старого питання, і я думаю, що я міг би додати інший підхід до цієї проблеми.

Більшість відповідей тут стосуватиметься належного поводження з керуванням циклу, як його було запропоновано, але якщо список даних__ може бути руйнівним, я б запропонував вам перенести елементи зі списку, поки ви не виявите порожній список:

while True:
    element = element_list.pop(0)
    do_this_for_all_elements()
    if not element:
        do_this_only_for_last_element()
        break
    do_this_for_all_elements_but_last()

ви навіть можете використовувати while len (element_list), якщо вам не потрібно нічого робити з останнім елементом. Я вважаю це рішення більш елегантним, ніж розгляд з next ().


2

Для мене найпростішим і пітонічним способом обробки спеціального випадку в кінці списку є:

for data in data_list[:-1]:
    handle_element(data)
handle_special_element(data_list[-1])

Звичайно, це також може бути використане для обробки першого елемента особливим чином.


2

Замість того, щоб рахувати, ви також можете рахувати:

  nrToProcess = len(list)
  for s in list:
    s.doStuff()
    nrToProcess -= 1
    if nrToProcess==0:  # this is the last one
      s.doSpecialStuff()

1

Затримати спеціальну обробку останнього предмета до закінчення циклу.

>>> for i in (1, 2, 3):
...     pass
...
>>> i
3

1

Існує кілька способів. нарізка буде найшвидшою. Додавання ще одного, який використовує метод .index ():

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]

0

Припускаючи введення як ітератор, ось спосіб використання tee та izip з itertools:

from itertools import tee, izip
items, between = tee(input_iterator, 2)  # Input must be an iterator.
first = items.next()
do_to_every_item(first)  # All "do to every" operations done to first item go here.
for i, b in izip(items, between):
    do_between_items(b)  # All "between" operations go here.
    do_to_every_item(i)  # All "do to every" operations go here.

Демонстрація:

>>> def do_every(x): print "E", x
...
>>> def do_between(x): print "B", x
...
>>> test_input = iter(range(5))
>>>
>>> from itertools import tee, izip
>>>
>>> items, between = tee(test_input, 2)
>>> first = items.next()
>>> do_every(first)
E 0
>>> for i,b in izip(items, between):
...     do_between(b)
...     do_every(i)
...
B 0
E 1
B 1
E 2
B 2
E 3
B 3
E 4
>>>

0

Найбільш просте рішення, яке мені спадає на думку:

for item in data_list:
    try:
        print(new)
    except NameError: pass
    new = item
print('The last item: ' + str(new))

Тому ми завжди заздалегідь дивимось на один предмет, затримуючи обробку на одну ітерацію. Щоб пропустити щось під час першої ітерації, я просто виявляю помилку.

Звичайно, вам потрібно трохи подумати, NameErrorщоб піднятися, коли ви цього хочете.

Також зберігайте `приказку

try:
    new
except NameError: pass
else:
    # continue here if no error was raised

Це означає, що назва new раніше не була визначена. Якщо ви параноїк, ви можете переконатися, що newйого немає, використовуючи:

try:
    del new
except NameError:
    pass

З іншого боку, ви також можете використовувати оператор if ( if notfirst: print(new) else: notfirst = True). Але наскільки я знаю, накладні витрати більші.


Using `timeit` yields:

    ...: try: new = 'test' 
    ...: except NameError: pass
    ...: 
100000000 loops, best of 3: 16.2 ns per loop

тому я очікую, що накладні витрати будуть невідбірними.


0

Порахуйте елементи один раз і слідкуйте за кількістю залишилися елементів:

remaining = len(data_list)
for data in data_list:
    code_that_is_done_for_every_element

    remaining -= 1
    if remaining:
        code_that_is_done_between_elements

Таким чином ви оцінюєте лише довжину списку лише один раз. Здається, багато рішень на цій сторінці припускають, що довжина заздалегідь недоступна, але це не є частиною вашого питання. Якщо у вас є довжина, використовуйте її.


0

Одне просте рішення, яке приходить вам на думку, було б:

for i in MyList:
    # Check if 'i' is the last element in the list
    if i == MyList[-1]:
        # Do something different for the last
    else:
        # Do something for all other elements

Друго не менш просте рішення можна досягти за допомогою лічильника:

# Count the no. of elements in the list
ListLength = len(MyList)
# Initialize a counter
count = 0

for i in MyList:
    # increment counter
    count += 1
    # Check if 'i' is the last element in the list
    # by using the counter
    if count == ListLength:
        # Do something different for the last
    else:
        # Do something for all other elements
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.