'for'루프에서 마지막 요소를 감지하는 pythonic 방법은 무엇입니까?


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.;)


첫 번째 사람은 어떻습니까? 그것도 억제되어야합니까?
Adam Matan

요소간에 수행되는 작업을 알려주시겠습니까?
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()

이것은 iterable이없는 경우에도 가능합니다 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와 iterable이없는 iterable의 현재 값이 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
사실,이 방법은 내 것보다 낫습니다. 적어도 enumerate와 len을 사용할 필요는 없습니다.
e.tadeu

예, 그러나 if루프가 두 개의 루프로 분할되면 피할 수있는 다른 것을 추가합니다 . 그러나 이는 거대한 데이터 목록을 반복 할 때만 관련이 있습니다.
Adam Matan

두 개의 루프로 나누는 문제는 DRY를 위반하거나 메소드를 정의하도록 강요한다는 것입니다.
e.tadeu

나는 당신의 마지막 예제 (내 코드에서 완벽하게 작동하는)를 이해하려고 노력하지만, 그것이 어떻게 작동하는지 이해하지 못한다 (뒤에있는 아이디어)
Olivier Pons

1
@OlivierPons Python의 반복자 프로토콜을 이해해야합니다. 객체의 반복자를 얻고를 사용하여 첫 번째 값을 검색합니다 next(). 그런 다음 반복자가 자체적으로 반복 가능하다는 것을 악용하므로 for소진 될 때까지 루프 에서 사용할 수 있습니다 . 두 번째 값에서 마지막 값으로 반복합니다. 이 동안 나는 현재 값을 반복자에서 로컬로 검색 yield하고 마지막 값을 유지합니다 . 이런 식으로 내가 올 가치가 하나 더 있다는 것을 안다. for 루프 후 마지막 값을 제외한 모든 값을보고했습니다.
Ferdinand Beyer

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이 예에서, 프로그램은 잠시 동안 실행됩니다. appendafaik을 권장합니다. l=[1,2,3]; l.append(4);
plhn

18

'코드 사이'는 헤드-테일 패턴 의 예입니다 .

아이템이 있고 그 뒤에 (item) 쌍의 시퀀스가옵니다. 이 항목을 순서대로 (항목, 사이) 쌍으로 볼 수도 있습니다. 일반적으로 첫 번째 요소를 특별하고 다른 모든 요소를 ​​"표준"사례로 사용하는 것이 더 간단합니다.

또한 코드 반복을 피하려면 반복하지 않으려는 코드를 포함하는 함수 또는 다른 객체를 제공해야합니다. 루프에 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 조건을 한 번만 제외하고 항상 거짓 인 if 조건을 많이 낭비하지 않아도 됩니다.


4
함수 호출은 if명령문 보다 느리 므로 "폐기 된 실행"인수가 유지되지 않습니다.
페르디난트 베이어

1
함수 호출과 if 문 사이의 속도 차이가 무엇과 관련이 있는지 잘 모르겠습니다. 요점은이 공식에는 항상 거짓 인 if 문이 없다는 것입니다 (한 번만 제외)
S.Lott

1
나는 당신의 진술을“… 그리고 한번만 제외하면 항상 거짓 인 if 조건의 많은 낭비 된 실행을 필요로하지 않는다”고 해석했다 if. 분명히 "코드 청결도"를 언급하고 있습니까?
페르디난트 베이어

if파이썬 커뮤니티에서는 실제로 명령문 을 사용하는 대신 함수를 정의하는 것이 더 깔끔하다고 생각합니까?
Markus von Broady

17

마지막 요소를 수정하려는 경우 data_list간단히 표기법을 사용할 수 있습니다.

L[-1]

그러나 그 이상을하고있는 것처럼 보입니다. 당신의 길에는 아무런 문제가 없습니다. 템플릿 태그에 대한 일부 장고 코드간략히 살펴 보았으며 기본적으로 수행중인 작업을 수행합니다.


1
나는 그것을 수정하지 않고 그것을 사용하여 무언가를하고있다
e.tadeu

4
@ e.tadeu 수정 여부에 관계없이 중요하지 않습니다. if 문을 :로 변경 if data != datalist[-1]:하고 다른 모든 것을 동일하게 유지하는 것이 내 의견으로는 이것을 코딩하는 가장 좋은 방법입니다.
spacetyper

8
@spacetyper 마지막 ​​값이 고유하지 않으면 중단됩니다.
Ark-kun

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

입력 데이터 위에 슬라이딩 창을 사용하여 다음 값을 살짝보고 센티넬을 사용하여 마지막 값을 감지 할 수 있습니다. 이것은 모든 iterable에서 작동하므로 미리 길이를 알 필요가 없습니다. pairwise 구현은 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

마지막 요소를 모두 반복하고 마지막 요소를 루프 외부에서 처리 할 수 ​​있습니까? 결국, 루프는 루프하는 모든 요소와 유사한 것을 수행하기 위해 만들어집니다. 하나의 요소에 특별한 무언가가 필요한 경우 루프에 있으면 안됩니다.

(이 질문도 참조하십시오 : 마지막 요소에 루프 루프가 필요합니다. 별도의 처리 )

편집 : 질문은 "사이에"에 관한 것이기 때문에 첫 번째 요소는 전임자가 없다는 점에서 특별한 요소 이거나 마지막 요소는 후임자가 없다는 점에서 특별합니다.


그러나 마지막 요소는 목록의 다른 모든 요소와 유사하게 취급해야합니다. 문제는 요소 간에 만 수행해야하는 문제입니다 .
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처리하기 전에 해당 목록이 비어 있지 않은지 확인하거나 확인이 필요하지 않은 경우에도 사용할 수 있습니다 (ouch!).

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 방식으로 작업을 수행하면 다음이없는 것을 포함하여 모든 iterable과 작동합니다 __len__.

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

출력 :

1
2
3
3

내가 그렇게하는 것처럼 느끼면 나에게 단순 해 보입니다.


2
하지만 참고 반복 가능한 [-1]에 작동하지 않습니다 모든 (하지 않는 등의 발생으로 반복 가능 객체 )
e.tadeu

루프 후 마지막 항목에 액세스하기 만하면을 사용 item하여 다시 계산하는 대신 사용 하면됩니다 list[-1]. 그러나 그럼에도 불구하고 나는 이것이 OP가 요구 한 것이라고 생각하지 않습니다.
Ferdinand Beyer

다시 : 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>

주의 사항 : 이것은 목록의 모든 요소가 실제로 다른 경우에만 작동합니다 (메모리에 다른 위치가 있음). 후드 아래에서 파이썬은 동일한 요소를 감지하고 동일한 객체를 재사용 할 수 있습니다. 예를 들어, 동일한 값과 공통 정수의 문자열입니다.


2

목록을 살펴보면 나에게도 효과가 있었다.

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

2

Google 은이 오래된 질문에 나를 데려 왔으며이 문제에 다른 접근법을 추가 할 수 있다고 생각합니다.

여기에있는 대부분의 대답은 요청 된대로 for 루프 제어를 올바르게 처리하는 것이지만 data_list가 파괴 가능한 경우 빈 목록으로 끝날 때까지 목록에서 항목을 팝업하는 것이 좋습니다.

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()

마지막 요소로 아무것도 할 필요가 없다면 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

반복자로 입력을 가정하면 itertools에서 tee와 izip을 사용하는 방법이 있습니다.

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.