본문 바로가기

취미코딩/python

for 안에서 list.pop()을 쓰면 안되는 이유

pop은 아래처럼 쓴다.

>>> a = [1, 2, 3, 4, 5]
>>> a.pop()
5
>>> a
[1, 2, 3, 4]
>>> a.pop(0)
1
>>> a
[2, 3, 4]

 

그러니까 리스트에서 인자값 없이 pop()을 호출하면 마지막 요소를 꺼낸다. 인자값으로 인덱스를 넣으면 해당 인덱스의 값을 꺼낸다. 꺼낸 값은 다른 변수에 할당해도 되고, 리스트 자체는 바로 바뀐다. 그럼 아래 예의 결과는 어떻게 될까.

a = [1,2,3,4,5]

for i, v in enumerate(a):
    if v < 5:
        a.pop(i)

 

enumerate()는 리스트의 인덱스를 함께 반환한다. 위에서 i가 인덱스넘버, v가 값이다. 5보다 작으면 해당 인덱스를 pop하라고 하니, 결과는 [5]일 것이다. 실제로 해보라. 결과는 [2, 4, 5]다. 왜 그럴까?

왜냐면 pop은 즉각적으로 실행되기 때문이다. 즉, 첫번째 반복에서 1은 5보다 작기 때문에 pop(0)이 실행되고 리스트에서 바로 제거된다. 그 다음 반복은 2번 인덱스인데(i가 1), 이미 1이 꺼내어진 [2,3,4,5]의 반복이 되므로 v는 2가 아닌 3이 된다. 3이 pop되면 그 다음 인덱스에 해당하는 값은 5가 된다. 이해가 쉽지 않다면 아래 코드로 실행해보자. 저 반복문은 3번 순회한다. 5번이 아니라.

a = [1,2,3,4,5]

for i, v in enumerate(a):
    print(i, v)		// 몇번째 요소를 돌고 있는지, 그리고 해당 값이 무엇인지
    print(a)	// pop 되기 전 리스트
    print('__________')
    if v < 5:
        print('pop!')
        a.pop(i)
    else:
        print('go on')

    print(a)	// 다음 반복문으로 넘어가기 전 리스트
    print("==========")

 

그래서 리스트를 반복시키면서 조건에 맞는 값을 pop 하겠다는 발상은 위험하다. 그러면 어떻게 해야 할까. 파이썬의 멋진 list comprehension을 써보는 건 어떨까.

a = [1,2,3,4,5]

result = [v for v in a if v > 4]

 

3줄이 1줄로 줄어드는 덤까지 얻었다.

의외로 많은 블로그에서 저 부분을 간과하고 예제 코드를 실어서 잘못 알고 쓰는 이들이 많은 것 같아 정리했다. 대량의 데이터일 경우 저 두 방법으로 나온 결과의 차이점을 알기 쉽지 않다(두 방법을 다 알고 있다면 len()으로 비교하면 된다. 다만 직접 비교하지 않고 넘어가는 경우가 많을 것이고, 원인을 모른다면 골머리를 앓을 것이다).