AIFFEL에서 배운 iterator, generator, decorator 를 정리하자.
이터레이터를 이해하기 위해서는 먼저 iterable을 이해해야 한다.
# for 반복문
for 반복자 in 반복할 수 있는 객체
iter( ) 라는 함수를 통해 이 클래스가 iterable한 객체인지 확인 할 수 있다.
클래스의 개념을 정확히 알고 있다면 조금 더 자세히 이해할 수 있다.
이때 __iter__() 는 __next__() 메소드를 호출하여 계속 다음 값을 불러옴
확인을 위해 구현해보면,
원래 next 기능인 다음 값이 아닌 오버라이딩한 '동작'이 출력되는 것이 확인 가능
iterable 객체는 다음 값을 반환하는 기능이 없으니 iter를 통해 iterator 호출
명확히 다시 정리하자면,
위와 같이 우리는 iterator에서 next 메소드를 사용하여 다음 값들을 반환받을 수 있다.
(물론 지금은 '동작'을 반환하지만 )
for 반복자 in (iterable 객체 )
for문은 __iter__를 통해 내부적으로 iterator를 호출한다고 생각하면 된다.
Q) dictionary는 순서가 없는 collection 인데 왜 다음 값을 받아올 수 있지?
A) 순서가 있다는 sequence (list, tuple, str 등)와 iterable은 다른 개념
간단히 말하면, iterator를 생성하는 함수이다.
iterator 생성법
- generator로 구현
- class로 구현 ( 위에서 만든 __iter__와 __next__를 사용한 MyIterator )
일반적인 함수는 수행문이 끝나야 나머지 부분 진행가능, 제너레이터는 그렇지 않음
→ 상태를 기억한 채 요청이 들어오면 제너레이터 실행 ( 느긋한 계산법 = Lazy Evaluation )
generator는 함수면서, iterator 이다.
보면, 맨 아래에 __next__가 있는 것을 알 수 있다. ( 물론 중간에 짜름 )
generator 생성법
- 함수를 통한 구현 ( return 대신 yeild 사용 )
- generator 표현식
# 괄호 앞에 tuple을 적으면 tuple comprehension이다.
tuple_com = tuple(x for x in range(10))
generator 표현식의 경우 tuple comprehension과 비슷
yield가 함수 내부에 사용되고 있다면 generator 함수이다.
만약 값을 return 받기 위해서는 어떻게 해야할까?
우리는 1,2,3 이 나오기를 기대하지만 이런 식으로 출력하면 1,1,1 만 나온다.
Q) why?
A) 호출 연산자 ( )를 사용해서 generator를 호출하니 새로운 generator를 생성한다.
제대로 상태를 기억하며 진행하기 위해서는
generator 함수를 변수에 바인딩(binding) 시켜야 generator를 한번만 호출하여 상태 기억
+) 바인딩
메모리가 할당된 공간을 객체라고 한다. 그런 공간(메모리)를 식별자가 가리키는 것
a = 10
메모리를 형상화하여 보면,
yield를 하고 상태를 유지하고 다시 next가 호출되면 yield하는 과정이 반복된다.
Q) 왜 yield라고 할까?
A) yield는 말그대로 '양보하다' 라는 뜻으로, 함수 외부로 진행을 양보함을 의미
그럼 정확히 어디에서 대기 상태가 되는 걸까?
yield 1 이 실행되자마자, 대기 상태로 들어가는 것을 알 수 있다.
우리는 generator가 yield가 호출되면 대기상태로 들어가는 것을 이용하여 무한한 return을 구현할 수 있다.
이렇게 대기상태일때, 바로 반복문을 이용해 yield할 값을 변경해주면 된다.
→ Lazy Evaluation
또다른 예시를 보자.
함수로 구현하게 되면, 함수가 실행이 종료될 때까지 진행이 함수에서만 일어난다.
그래서 함수의 호출이 5번 모두 일어난 후에 done을 출력하는 것을 볼 수 있다.
함수가 아닌 generator로 구현하면, 5번이 한번에 출력이 나오는 것이 아닌 return을 하고 나면 상태를 기억하고 대기상태에 돌입하는 것을 볼 수 있다.
for문과 generator의 관계
우리는 generator까지 공부하게 되면서, for문을 정확히 이해할 수 있다.
for i in range(10):
수행문
과정
1. for문을 실행하면, iterable한 객체에서 값을 차례로 뽑는 iterator를 생성한다. ( __iter__ )
2. __next__를 호출하여 iterator 포인터가 가르키는 첫번째 값을 반환
3. 반복자 i에 반환되면 yield 호출 상태임으로 수행문이 진행되는 동안 대기
4. 다시 수행문이 끝나고 for문이 호출되면 다시 __next__
5. 반복
반복해서 사용하는 특정 함수 코드를 효율적으로 사용하기 위해 사용
함수에 추가적인 기능 추가 가능