-
Python - 제너레이터개발 2023. 2. 14. 16:49
제너레이터
파이썬에는 return 대신 yield 로 결과 값을 리턴하는 코드가 있는데 이 함수는 제너레이터를 생성하는 함수다. 제너레이터는 일반적으로 알고 있는 리스트랑 유사하다.
def multiple_list(number, limit): counter = 1 value = number * counter ret = [] while value <= limit: ret.append(value) counter += 1 value = number * counter return ret def multiple_generator(number, limit): counter = 1 value = number * counter while value <= limit: yield value counter += 1 value = number * counter
위의 코드에서 multiple_list와 multiple_generator는 모두 내부 로직에서 차이가 없다. 대신 multiple_list는 결과를 리스트로 줬고 아래 multiple_generator는 결과 값을 제너레이터로 준다.
ml = multiple_list(500, 5000) mg = multiple_generator(500, 5000) for ml_num, mg_num in zip(ml, mg): print(f"ml_num: {ml_num}, mg_num: {mg_num}") ml_num: 500, mg_num: 500 ml_num: 1000, mg_num: 1000 ml_num: 1500, mg_num: 1500 ml_num: 2000, mg_num: 2000 ml_num: 2500, mg_num: 2500 ml_num: 3000, mg_num: 3000 ml_num: 3500, mg_num: 3500 ml_num: 4000, mg_num: 4000 ml_num: 4500, mg_num: 4500 ml_num: 5000, mg_num: 5000
실행한 결과 출력값도 동일하다. 제너레이터나, 리스트나 결과 값이 동일하다면 굳이 따로 가져갈 이유는 없다. 그런데 제너레이터는 리스트와 결과물을 가져오는 방식이 다르다. 제너레이터는 데이터를 호출하는 시점에 코드를 실행하고 그 전까지는 함수 실행을 중지한다. for 문을 돌기 전까지는 어떤 코드를 돌릴 예정이라고만 알릴 뿐 실제로 어떤 값을 갖고 있는지는 모른다.
ml = multiple_list(500, 5000) mg = multiple_generator(500, 5000) print(f"ml: {ml}") print(f"mg: {mg}") ####################################### ml: [500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000] mg: <generator object multiple_generator at 0x105142a40>
print로 값을 출력해보면 리스트는 이미 결과 값을 갖고 있는 반면 제너레이터는 결과 값은 없고 주소값만 딸랑 있다. 아직 어떤 데이터가 있는지 모르기 때문이다.
import time def print_time(*args): print(time.strftime("%c", time.localtime(time.time())), *args) def multiple_generator(number, limit): counter = 1 value = number * counter while value <= limit: yield value print_time(f"yield value {value}") counter += 1 value = number * counter mg = multiple_generator(500, 5000) print(mg) for num in mg: print_time(f"print_num {num}") sleep(1)
자세한 제너레이터 동작 방식을 알기 위해 중간에 sleep을 넣어봤다. 수정한 multiple_generator 함수에는 yield 바로 아래 print_time를 넣었다. 이 함수는 값을 호출하는 것 뿐만 아니라 호출하는 시점의 시간도 같이 출력한다. multiple_generator 상에서 값을 yield value를 호출하는 부분과 print_time 사이에 어떤 코드도 없기 때문에 로그상으로는 두 코드 호출 시점에 시간차가 없어야 한다. 그런데 실제 결과 값은 다음과 같다.
Tue Feb 14 16:24:17 2023 print_num 500 Tue Feb 14 16:24:18 2023 yield value 500 Tue Feb 14 16:24:18 2023 print_num 1000 Tue Feb 14 16:24:19 2023 yield value 1000 Tue Feb 14 16:24:19 2023 print_num 1500 Tue Feb 14 16:24:20 2023 yield value 1500
print_num 이 호출되는 시점과 yield value가 호출되는 시점 사이에 1초간 차이가 난다. 1초는 for 문 내에서 딜레이를 1초간 줬기 때문에 발생한 것이다. 실제 코드는 아래 순서대로 동작한다.
1. yield value
2. for 문 내의 sleep(1)
3. counter += 1
4. value = number * counter
5. yield value
제너레이터에선 호출 시점에 데이터를 접근하고 그전까지는 함수의 실행을 중지한다. yield를 기점으로 왔다갔다 하는 방식이다. 리스트와 다르게 모든 데이터를 한번에 리턴하지 않아도 되기 때문에 메모리를 효율적으로 사용할 수 있다.
인공지능의 경우 대량의 데이터를 가져와야하는 경우가 있는데 yield를 적절하게 사용하면 메모리 공간을 아끼면서 처리할 수 있어서 유용할 것 같다. 왜 인공지능 라이브러리들은 대부분 파이썬인지 알 것 같다.
'개발' 카테고리의 다른 글
Flask SQLAlchemy vs SQLAlchemy (0) 2023.02.14 Python - namedtuple (0) 2023.02.14 Python - 데코레이터 (0) 2023.02.14 Nodejs Blocking/Non-Blocking, (0) 2023.01.26 socketio redis adapter (0) 2023.01.06