2021. 8. 7. 22:41ㆍ분석 Python/구현 및 자료
yield에 대해서 생각보다 많이 쓰는 것 같은데, 너무 모르는 것 같아서 정리해보고자 합니다.
yield는 주로 Generator에서 쓰이게 되며, Python Generator를 정의하는 핵심은 "yield" 키워드를 사용하는 것이라고 합니다.
Python Generator는 대규모 collection이 필요한 시나리오 및 멀티 스레딩과 같은 기타 특정 시나리오 및 코드의 가독성을 향상하는 시나리오에서 널리 사용됩니다.
이 글에서 모든 것을 다룰 수는 없겠지만 기본적인 개념이나 고급 사용 방법에 대해서 적어보고자 합니다.
일단 generator를 알아보고자 합니다.
목차
Iterator란?
그 전에 알아야 하는 것은 Iterator입니다.
Iterator란 반복 가능한 객체, 즉 반복문을 이용해서 데이터를 순회하면서 처리하는 것을 의미합니다
iterator는 next() 메서드로 데이터를 순차적으로 호출 가능한 객체입니다.
x = ["a","b","c"]
y = iter(x)
next(y) # a
next(y) # b
next(y) # c
비슷한 말로는 iterable이 있다. 헷갈릴 수 있으니 적어보면 다음과 같습니다.
iterable이라는 것은 리스트를 만든 후 해당 리스트에 있는 객체를 순환하며 하나씩 꺼내서 사용할 수 있다고 하고 이러한 과정을 Iteration이라고 합니다. (iterable에 대표적인 것은 list , str , tuple이다)
x = ["a","b","c"]
for i in x :
print(i)
Generator란?
Generator란 Iterator를 생성해주는 함수를 의미합니다. Generator는 모든 값을 메모리에 담고 있지 않고, 그때그때 값을 생성해서 반환하기 때문에 제너레이터를 사용할 때는 한 번에 한 개의 값만 순환할 수 있습니다.
포인트는 모든 값을 포함하여 변환하는 대신 호출할 때마다 한 개의 값을 리턴한다는 것입니다.
즉, 그때그때 생성을 하니 메모리가 충분하지 않은 상황에서 대용량의 반복 가능한 구조로 순회할 수 있다는 것입니다.
yield를 같이 사용하면 Generator를 만들 수 있습니다.
yield가 호출되면 임시적으로 return이 호출되며, 한번 더 실행되면 실행되었던 이전 yield의 다음 코드가 실행됩니다.
그래서 아래의 예제를 보면, 처음 yield와 뒤의 yield 사이에 print가 있다고 했을 때 출력 결과가 다음 코드를 실행하는 것을 알 수 있습니다.
def generator():
yield "a"
print("aa")
yield "b"
print("bb")
yield "c"
gen = generator()
gen
# <generator object generator at 0x7f5c74734350>
next(gen) # a
next(gen) # aa , b
next(gen) # bb , c
names = ['Alice', 'Bob', 'Chris', 'David', 'Emily']
def gen_roster(names):
for name in names:
yield name
roster = gen_roster(names)
for name in roster:
print(name)
메모리도 실제로 보면 엄청난 차이가 발생합니다
import sys
a_list = [1 for i in range(10000000) ]
a_generator = (1 for i in range(10000000) )
sys.getsizeof(a_list) # 81528064 Bytes
sys.getsizeof(a_generator) # 112 Bytes
Generator
NEXT
위에서 적은 앞의 예에서 for 루프에서 생성기를 사용하는 것은 그다지 의미가 없습니다.
Generator를 사용하는 이점은 한 번에 하나의 값을 얻을 수 있다는 것입니다.
이것은 거대한 컬렉션을 다룰 때 매우 유용할 수 있습니다.
즉, Generator를 만들 때 항목이 메모리로 읽히지 않습니다. 다음 항목을 가져오려고 할 때 yield 키워드를 누르는 경우에만 항목이 생성됩니다.
따라서 반드시 생성기의 "NEXT" 요소를 얻는 것이 중요하다. 이 경우 __next__() 함수를 사용할 수 있습니다.
roster = gen_roster(names)
roster.__next__()
# Alice'
next(roster)
# 'Bob'
for 루프는 yield 키워드에 도달할 때까지 한 번만 실행된다고 생각할 수 있습니다.
이렇게 한 개씩 이동하는 것이 끝나면 에러로는 StopIteration이 발생합니다.
roster는 현재 로테이션에 대해 누가 대기 중인지 결정하는 데 사용되기 때문입니다.
즉, 모든 이름이 다시 반복되도록 "cursor"가 처음으로 돌아가려면 어떻게 해야 할까?
def gen_roster(names):
while True:
for name in names:
yield name
roster = gen_roster(names)
for i in range(12):
print(next(roster))
Alice
Bob
Chris
David
Emily
Alice
Bob
Chris
David
Emily
Alice
Bo
Send a Value
내가 이 글을 쓰게 된 목적이라고도 할 수 있는데, 실제 코드에서 send를 보고 당황한 적이 있습니다.
그러다가 어떻게든 이해를 하게 됐지만, 다시는 까먹지 않게 위해 정리를 시작하게 되었습니다.
Generator의 고급 사용법 중 하나는 Generator에 값을 보내는 것입니다.
이 값은 현재 yield 표현식의 결과가 되며 메서드는 Generator가 생성한 next을 반환합니다.
따라서 다음 값을 반환할 것이기 때문에 생성기가 방금 보낸 값을 반환할 것으로 기대하면 안 됩니다.
그러나 이것을 사용하여 생성기 내부에서 작업을 수행할 수 있습니다. 예를 들어, 무한 생성기에 특정 값을 전송하여 무한 생성기를 중지할 수 있습니다.
아래 예제에서는 무한 rotation generator에 특정 인덱스에서 roster에 stop이라는 값을 보내줘서 생성기를 멈춥니다.
그래서 평상시에는 None 값을 밥다가 i=3일 때 stop을 받아서 멈추게 됩니다.
그래서 3개까지는 받고 4번째부터 멈춘 것이라고 할 수 있습니다.
def gen_roster(names):
while names:
for name in names:
current_name = yield name
if current_name == 'stop':
names = None
break
roster = gen_roster(names)
for i in range(10):
if i == 3:
roster.send('stop')
print(next(roster))
send 메서드는 multi threading 프로그래밍 시나리오에서 생성기의 동작이나 규칙을 변경하려는 경우에 매우 유용합니다.
Stop a Generator - Throw Exception and Close
문제가 있는 경우 throw() 메서드를 사용하여 Generator가 일시 중지된 지점에서 예외를 발생시킬 수 있습니다.
오류 유형을 사용자 정의할 수 있습니다. 이 튜토리얼의 데모 목적을 위해 편의상 "TypeError"를 사용하겠습니다.
roster = gen_roster(names)
next_name = roster.throw(TypeError, 'Stop!')
TypeError: Stop!
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_3046911/815557122.py in <module>
1 roster = gen_roster(names)
----> 2 next_name = roster.throw(TypeError, 'Stop!')
/tmp/ipykernel_3046911/3167137861.py in gen_roster(names)
----> 1 def gen_roster(names):
2 while names:
3 for name in names:
4 current_name = yield name
5 print(current_name)
TypeError: Stop!
아무 문제가 없지만 여전히 Generator를 종료하고 싶다면 제너레이터의 close() 메서드를 사용할 수 있습니다. 이것은 무한 생성기가 있고 특정 지점에서 멈추고 싶을 때 매우 유용합니다.
roster = gen_roster(names)
for i in range(10):
if i == 3:
roster.close()
print(next(roster))
큰 데이터를 다룰 때 단순히 리스트가 아닌 generator를 만들어서 시도해봐야겠다.
https://towardsdatascience.com/how-to-use-yield-in-python-5f1fbb864f94
https://tech.ssut.me/what-does-the-yield-keyword-do-in-python/
https://scipy-lectures.org/advanced/advanced_python/index.html
'분석 Python > 구현 및 자료' 카테고리의 다른 글
Python) 회귀 분석 기본 사용법 정리(scikit-learn, statsmodels) (2) | 2021.08.11 |
---|---|
notion-py를 사용하여 캘린더 만들기 (0) | 2021.08.08 |
or-tools) 제한 조건 만족하는 특정 인자에 대한 경우의 수 찾기 (0) | 2021.08.01 |
pycaret 2.3.1) tune_model hyperopt example (0) | 2021.06.20 |
numpy에서 dict을 이용해서 값을 바꾸는 방법 (2) | 2021.06.06 |