1. 제너레이터 표현식.
리스트 컴프리헨션(Comprehension) 문법에서 대괄호, []를 소괄호, ()로 바꿔주면 제너레이터 표현식 (Generator Expression)이라는 문법을 만들 수 있음.
a = [
item * item
for item in range(0, 10)
if item % 2 == 0
]
print(f'type(a) = {type(a)}')
print(f'a = {a}')
a = (
item * item
for item in range(0, 10)
if item % 2 == 0
)
print(f'type(a) = {type(a)}')
print(f'a = {a}')
- 실행 결과-
type(a) = <class 'list'>
a = [0, 4, 16, 36, 64]
type(a) = <class 'generator'>
a = <generator object <genexpr> at 0x00000230A87DF2A0>
list_1 = [n * n for n in range(5)]
print("리스트: ", list_1)
generator_1 = (n * n for n in range(5))
print("제너레이터 객체: ", generator_1)
print("제너레이터 -> 리스트: ", list(generator_1))
- 실행 결과 -
리스트: [0, 1, 4, 9, 16]
제너레이터 객체: <generator object <genexpr> at 0x000001DAEAE1DA40>
제너레이터 -> 리스트: [0, 1, 4, 9, 16]
- 즉,
리스트 컴프리헨션의 경우 대괄호([])를 사용해서 리스트를 만들어서 모든 데이터를 한꺼번에 메모리에 올려 사용함.
- 그래서 데이터가 많을수록, 즉 리스트의 크기가 커질수록 메모리 사용량이 많아짐.
[표현식 for 변수 in 데이터]
제너레이터 표현식의 경우는 소괄호(())를 사용해서 제너레이터 객체를 만들어 놓고 필요할 때마다 데이터를 하나씩 생성함.
- 리스트와 달리 제너레이터는 필요할 때만 값을 하나씩 꺼내서 사용하는 방식이므로 메모리를 절약할 수 있음.
(표현식 for 변수 in 데이터)
print(
item * item
for item in range(0, 10)
if item % 2 == 0
)
- 실행 결과 -
<generator object <genexpr> at 0x000001D3114ADA40>
※ 위처럼 함수의 괄호로 둘러싸여 있는 경우에는 따로 소괄호(())를 사용하지 않아도 제너레이터 표현식으로 인식됨.
2. 리스트 컴프리헨션 -> 제너레이터 표현식으로 대체.
- 제너레이터 표현식 (Generator Expression)은 대부분의 경우 리스트 컴프리헨션(Comprehension)를 대체할 수 있기 때문에 가능하다면 대체하는 것이 좋음.
2-1. 가능하다면 대체하는 것이 좋음.
- why?
- 리스트 컴프리헨션을 이용하여 크기가 큰 리스트를 만들 경우 메모리 사용량이 많아지지만
제너레이터 표현식은 제너레이터 객체만을 만들 뿐임. 그래서 메모리 사용량이 적음.
2-2. 메모리 비교.
import sys
list_comp = [i for i in range(100000000)]
gen_exp = (i for i in range(100000000))
list_mb = sys.getsizeof(list_comp) / (1024 * 1024)
gen_mb = sys.getsizeof(gen_exp) / (1024 * 1024)
print(f'리스트 메모리 크기: {list_mb:.5f} MB')
print(f'제너레이터 메모리 크기: {gen_mb:.5f} MB')
- 실행 결과 -
리스트 메모리 크기: 796.44070 MB
제너레이터 메모리 크기: 0.00019 MB
2-3. 대체하면 안 되는 케이스.
2-3-1. 미리 처리해 두고 요청이 있을 때 즉시 데이터를 응답해야하는 경우.
origin = [1, 2, 3, 4, 5]
list_comp = [str(i) for i in origin]
print("리스트 컴프리헨션:", ', '.join(list_comp))
-------------------------------------------------------
origin = [1, 2, 3, 4, 5]
gen_exp = (str(i) for i in origin)
print("제너레이터 익스프레션:", ', '.join(gen_exp))
- 리스트 컴프리헨션의 경우
list_comp = [str(i) for i in origin] 이때 처리가 일어남.
제너레이터 표현식의 경우 print("제너레이터 익스프레션:", ', '.join(gen_exp)) 이때 처리가 일어남.
- 따라서 미리 처리해 두고 요청이 있을때 곧바로 데이터를 응답해야 하는 상황이라면
리스트 컴프리헨션을 사용하는 것이 적절함.
2-3-2. 원본의 변경이 반영되면 안 되는 상황.
origin = [1, 2, 3, 4, 5]
list_comp = [str(i) for i in origin]
gen_exp = (str(i) for i in origin)
origin[0] = 9999
print('리스트 컴프리헨션: ', ', '.join(list_comp))
print('제너레이터 익스프레션: ', ', '.join(gen_exp))
- 실행 결과 -
리스트 컴프리헨션: 1, 2, 3, 4, 5
제너레이터 익스프레션: 9999, 2, 3, 4, 5
리스트 컴프리헨션의 경우 원본을 변경하는 시점 전에 처리가 돼서 원본을 변경하더라도 반영되지 않음.
but 제너레이터 표현식의 경우 원본을 변경하는 시점 이후에 처리가 돼서 원본을 변경하면 반영이 됨.
3. 참고.
- 파이써닉 파이썬 (Pythonic Python)
- 혼자 공부하는 파이썬.