2019.03.28 TIL
(TIL은 스스로 이해한 것을 바탕으로 정리한 것으로 오류가 있을 수 있습니다)
- iterator와 generator는 너무나도 정리가 잘된 블로그가 있어서 첨부드립니다.
- [iterator & generator 원본] (https://nvie.com/posts/iterators-vs-generators/)
- [이터레이터와 제너레이터 한국어 번역] (https://mingrammer.com/translation-iterators-vs-generators/)
# 질문에 답하기
- iterator
- generator
전체적인 그림
Iterator
- next()함수에 의해서 값을 하나씩 반환한다. (next()함수 호출시점)
- Stopiteration
list의 iterator
>>> li = [1,2,3,4,5]
>>> it = item(li) # list의 내장함수 dir(list)를 통해 알 수 있다.
>>> print(type(it))
list_iterator
>>> next(it) #li의 값을 1부터 하나씩 반환한다.
>>>
>>> for e in li:
>>> print(e) #이거를 실행하는 순간 내부적으로 iter(li)가 만들어져서 하나씩 반환한다.
- 실제로 list는 미친듯이 느리다.
- 따라서 필요에 의해 list를 iterator로 바꾸어 사용한다.
나만의 iterator 만들기
>>>
>>> class Myiter:
>>> def __init__(self.li):
>>> self.continer = li
>>> self.index = 0
>>>
>>> def __iter__(self):
>>> return self
>>>
>>> def __next__(self):
>>> if self.index >= len(self.continer):
>>> raise StopIteration
>>> ret = self.container[self.index]
>>> self.index += 1
>>> return ret
>>>
>>> it_obj = Myiter([1,2,3,4,5])
>>>
>>> next(it_obj)
1
>>> next(it_obj)
2
>>> it = iter(it_obj) #중간에 iter을 만들어도 기존에 것 해당x
>>> type(it)
__main__.Mylter
>>> next(it)
3
파일을 불러와 iterator 추출하기
>>>
>>> class Reader:
>>> def __init__(self, filename):
>>> self.f = open(filename, 'rt')
>>>
>>> def __iter__(self):
>>> return self
>>>
>>> def __next__(self):
>>> a = self.f.readline()
>>> if a == '':
>>> self.f.close()
>>> raise StopIteration
>>> print(a)
>>>
Generator
제너레이터는 특별한 종류의 이터레이터이다.
제너레이터 역시 함수이다. 독특한 함수이다.
함수 body안에 yield라는 키워드가 있어야 한다.(이게 나오면 자동으로 generator이다)
next와 yield이 서로 서로에게 실행을 넘겨주며 호출한다.
yield는 양보하다 라는 뜻을 가지고 있다.
- 제너레이터 에러 막기
- 제너레이터로 피보나치 만들기
- coroutine function
- send()
- yield from
제너레이터 에러 막기
>>> def gen():
>>> print('gen start')
>>> yield 1
>>> print("a")
>>> yield 2
>>> print("b")
>>> yield 3
>>> return 'done'
>>>
>>> g = gen() #제너레이터 객체가 생성된다. 실행x
>>> print(g)
<generator object gen at 0x1060a85c8>
>>>
>>> next(g) # 함수를 위에서 내려오며 yield을 만날 떄까지 진행
gen start # yield가 1을 value로 반환한다.
>>>
>>> print(next(g))
a # yield 1을 반환하며 blocking되고 그 이후부터 실행
2 # yield 2를 만나면서 2를 반환하다.
>>>
>>> next(g) #b를 print하며 3을 반환하고 blocking
b
>>>
>>> #next(g)를 하게 되면 StopIteration error가 발생
>>> #에러를 막기 위해 try exception을 이용
>>>
>>> try:
>>> next(g)
>>> except StopIteration as exc:
>>> print exc.value
>>> pass
>>>
c
done
>>>
제너레이터로 피보나치 구현하기
>>> def fib():
>>> prev, curr = 0, 1
>>> while True:
>>> yield prev #prev 넣으면 0부터 curr 을 넣으면 1부터
>>> prev, curr = curr, prev+curr
>>>
>>> f = fib()
>>> for i in range(10):
>>> print(next(f), end = ' ')
0 1 1 2 3 5 8 13 21 34
>>>
coroutine function
- 원래 함수는 실행할 때 스텍프레임이 생성되었다가 종료되면 스텍프레임이 없어진다. 하지만 제너레이터를 보면 실행 중에 멈추어서 다른 일을 하다가 또 호출을 해도 그 이전의 진행했던 사항들을 기억하고 있다. 이것을 가능하게 해주는 것이 coroutine function이다.
함수 실행 도중에 실행 주도권을 다른 함수에 넘길 수 있고, 내가 원하는 시점에 다시 실행 주도권을 가져올 수 있는 함수!!
함수 실행 도중이라는 것은 스텍프레임을 쌓아놓고 다른 곳에 실행주도권을 주는데 그렇게 하기위해 어디에 스텍프레임이 살아 있어야 한다.
명시적으로 실행주도권을 넘긴다.
원래는 os의 스케줄러가 라운드 로빈을 통해 선점형으로 실행되는데 내가 원하는 때 준다는 것은 비선점형이다.
중요한 것은 파이썬의 스텍 프레임이 heap에 할당된다는 것이다. 파이썬의 스텍프레임이 실행주도권을 넘기더라도 살아있을 수 있다. (힙에 저장되어 있으므로)
다른 언어에는 coroutine이 없는가라는 질문이 있을 수 있다.
c나 c++에서는 언어차원에서는 지원하지 않으나,
c : finite state machine기법 -> FSM
- 엔트리포인트의 위치를 적절하게 저장하여서 구현(실제로는 스텍프레임이 사라지지만 실행의 순서가 이어지듯이 구현
c++ : stack swapping
- 이미 스텍프레임에 쌓인 것은 어쩔수 없지만 heap영역에 마음대로 쌓을 수 있다. stack와 heap이 서로 오가며 스텍프레임을 쌓음
위와 같은 방법을 통해 해결한다.
파이썬의 코루틴은 generator 기반으로 만들어졌고 파이썬의 스텍프레임이 heap에 저장되므로 가능하다.
send()
- send()는 제너레이터의 내장함수이다.
- next는 실행주도권을 넘기는꺼지 가능하지만 send를 통해 실행주도권뿐만 아니라 데이터까지 함께 넘길 수 있다.
코드를 통해 send를 알아보자.
>>> def gen():
>>> print('gen start')
>>> deta1 = yield 1 #yield 1이 실행되면 blocking이 걸리므로 아직 data1 변수는 만들어지지 않는다.(오른쪽에서 왼쪽으로 이동)
>>> print(f"gen[1] : {data1}")
>>> data2 = yield 2
>>> print(f"gen[2] : {data2}")
>>> return 'done'
>>> g = gen()
>>> g
generator object gen at 0x1060a8a40
>>>
>>> first = g.send(None) #보통 시직할 때는 None을 넣어준다.
gen start
>>>
>>> first #yield 1로부터 1을 받아온다.
1
>>>
>>> second = g.send('world') #yield 1에 다시 'world'를 보내준다.
gen[1] : world
>>> second # yield 2로부터 2를 받아온다.
2
>>>
>>> try:
>>> g.send('hello')
>>> except StopIterator as exc:
>>> print(exc.value)
gen[2] : Hello
done
>>>
yield from
- delegate(중계)만을 한다.
- 아직까지 명확하게 어떻게 사용해야 할지는 감이 오지 않는다.
>>> def gen():
>>> print('gen start')
>>> deta1 = yield 1
>>> print(f"gen[1] : {data1}")
>>> data2 = yield 2
>>> print(f"gen[2] : {data2}")
>>> return 'done'
>>>
>>> def delegate():
>>> g = gen()
>>> print('start')
>>> ret = yield from g #g는 따로 관여하지 않고 delegate만 한다. gen()
>>> print('end')
>>> print(f'return value:{ret}')
>>> return ret
>>>
>>> g = delegate()
>>> print(g)
generator object delegate
>>> first = g.send(None) #yield from g를 받게 되면 gen으로 이동하며 gen실행이 끝날 때까지 다시 delegate로 내려 올 수 없다.
start
gen start
>>> print(first)
1
>>> second = g.send('world')
gen[1] : world
>>> second
2
>>> g.send('hello')
gen[2] : hello
end
return value : done
StopIteration Traceback (most recent call last)
<ipython-input-130-40ccd6fc328d> in <module>
----> 1 g.send('hello')
StopIteration: done
>>>
- yield from에 대해서는 어떤 상황에 쓰는지 좀 더 공부를 해봐야 할 것 같다.
'Python' 카테고리의 다른 글
파이썬 20. 크롤링 기본(HTML과 선택자) (0) | 2019.12.29 |
---|---|
파이썬 19. 파이썬 랜덤모듈 관련 (0) | 2019.12.28 |
파이썬 17. 깊은 복사 얕은 복사 (0) | 2019.12.28 |
파이썬 16. 객체지향(OOP) (0) | 2019.12.28 |
파이썬 15. 클래스간의 관계 (0) | 2019.12.28 |