티스토리 뷰

컴프리헨션과 제너레이터 수식을 살펴보겠습니다. 제너레이터는 좀 어려운 개념이긴 하지만 파이썬에서 정말 중요한 핵심이라고 할 수 있고 파이썬의 강력한 힘은 여기서 나온다고 해도 과언이 아닙니다.

컴프리헨션은 이전의 포스트에서 다루었는데, 사실 리스트 컴프리헨션이라고 하는 것이 더 적합합니다. 리스트를 생성하거나 변환하는 일을 아주 간결한 코드로 강력하게 할 수 있는 기능입니다. 이전 포스트에서도 얘기했지만 리스트 컴프리헨션은 파이썬의 가장 강력하고 편리한 기능 중 하나입니다. 

comp_list = [x*x for x in mylist if x % 2 == 1]  --- (i)

제너레이터 수식은 리스트 컴프리헨션과 똑같이 만들어지는데 대괄호 [  ] 대신 괄호 (  )를 씁니다.

gen_expr = (x*x for x in mylist if x % 2 == 1)  --- (ii)

괄호가 파이썬에서는 튜플이라는 자료구조를 표현하는데 여기서는 튜플이 아니라 제너레이터를 생성하는 수식입니다. 즉 gen_expr은 수식이고 사용될 때 계산됩니다. 제너레이터는 연속된 값을 차례로 하나씩 돌려주는 함수와 비슷한데, 값을 차례로 생성해서 돌려줍니다(yield). 제너레이터는 실시간(on the fly)으로 요청될 때마다 값을 하나씩 생성해 주는 코드 부분(수식 또는 함수)이라고 볼 수 있습니다.

리스트 컴프리헨션과 제너레이터 수식의 차이를 비교해 보죠. 위의 x*x를 생성하는 코드 (i)과 (ii)를 비교하면 mylist가 아주아주 클 때 몇 가지 차이가 있습니다.

(차이점 1) (i) 컴프리헨션은 제곱 리스트 comp_list를 모두 만들어서 돌려준다. 즉 메모리에 그만한 크기의 리스트가 하나더 생긴다.  반면 (ii)의 결과인 gen_expr 수식은 수식만 돌려준다. 실제로 제곱 값은 필요할 때 계산한다. 제너레이터 수식은 for 루프에서 쓰이거나 next() 를 이용해 호출할 때 n*n이 하나씩 계산된다. 그래서 아주 큰 자료구조나 값의 연속을 처리하는데도 효율적인 계산이 가능하다. 

(차이점 2) (i) 컴프리헨션은 리스트가 아주 큰 경우 결과가 나오기까지 오래 걸릴 수 있다. 모든 계산이 다 끝나야 첫번째 값 comp_list[0]를 쓸 수 있다. 반면에 (ii)번 수식은 값을 사용할 때 계산한다. 제너레이터 수식은 하나씩 돌려주고 그 다음 요청이 있을 때 다음 것을 계산한다. 그러므로 끝까지 값이 한꺼번에 다 필요로 하는 것이 아니고 하나씩 처리하면 될 때 요청해서 쓰기에 적합하다. 그러므로 만약 다음과 같은 엄청나게 큰 수의 계산이라면 제너레이터 수식이 유리할 것이다. 첫번째 컴프리헨션은 아마도 숫자가 아주 크다면(무한에 가까운 값) 엄청난 시간이 걸리지만 제너레이터 수식은 한개씩 요청될 때마다 계산하므로 별 문제가 없다. 제너레이터 수식은 무한한 계산도 가능하다는 장점이 있다.

[x for x in range(very_big_number) if isprime(x)]
(x for x in range(very_big_number) if isprime(x))

(제너레이터의 한계) 대신 (ii)번의 gen_expr 수식은 한번 차례로 호출하여 끝을 만나면 더이상 사용할 수 없다. 즉 한 번만 쓸 수 있다. 재사용 불가 처음부터 새로 시작하게 하고 싶다면 다른 방법을 써야 한다. 또한 인덱스로 몇번째 요소를 접근하는 것은 할 수 없다. 즉 gen_expr[0]이나 gen_expr.get(i) 이런 접근은 허용되지 않는다. 또한 len(gen_expr) 이런 것도 할 수 없다. 인덱스로 접근이나 길이 등도 사용할 수 없다. 아직 실제 데이터를 갖지 않는 코드 상태라고 볼 수 있다.

제너레이터는 for  in 이나 이터레이터의 next() 호출로만 접근 가능하다. 이터레이터는 실제 반복을 시켜주는 역할을 하는 객체입니다. 제너레이터는 이터러블(이터레이터를 제공할 수 있는 객체)이고 여기에 iter()를 취해 이터레이터를 돌려받습니다. (이것에 대해서는 파이썬의 이터레이터와 이터러블에서 다루고 있습니다.)

for a in gen_expr:
    print(a)

이터레이터란 값을 next() 메소드로 하나씩 돌려받을 수 있는 객체(인터페이스)다. 다음은 my_iterator라는 변수를 이용해서 값을 받는 예제입니다.

>>> print(gen_expr) 
<generator object <genexpr> at 0x000002047D1C2D00> 
>>> my_iterator = gen_expr
>>> print(my_iterator) 
<generator object <genexpr> at 0x000002047D1C2D00> 
>>> next(my_iterator) 
9 
>>> next(my_iterator) 
25 
>>> next(my_iterator) 
49 
>>> next(my_iterator) 
Traceback (most recent call last): File "<pyshell#10>", line 1, in StopIteration

이터레이터는 이런 식으로 next 함수를 통해 값을 하나씩 돌려받는 인터페이스인데, 여기서 더이상 값이 없을 때 StopIteration이 발생합니다. 이것은 별도로 처리되어야 하는데, 사실 for in은 이런 이터레이터를 반복하다가 StopIteration이 발생하면 종료하는 Syntactic Sugar라고 볼 수 있습니다.

동영상 강의 링크

youtu.be/suWqopF96ig

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함