[Python] 제네레이터(Generator)


Study/Python  2019. 12. 10. 21:21

안녕하세요. 명월입니다.


이 글은 Python에서 제네레이터(Generator)에 대한 글입니다.


Python에서 제네레이터는 C#에서 yield 키워드와 비슷한 문법입니다. 이 글을 작성하면서 알았는데 C#에서도 yield 문법을 제네레이터라고 하는군요.

링크 - [C# 강좌 - 57] yield 키워드

제네릭과 혼동이 될 수 있는 키워드입니다. (사실 저도 처음에는 혼동했었습니다.)

def example(first, last, step):
  # number 처음 수를 정의한다.
  number = first
  while number < last:
    # 현재의 number 값을 리턴한다.
    yield number
    # number 값을 step치 만큼 더한다.
    number += step

data = example(1,10,1)
print(data)
# data는 1부터 10까지 1만큼 증가하는 제네레이터를 가지고 있다.
# for를 사용해서 루프를 돌리면 10까지 데이터가 돌아가고 현재의 값이 i가 나온다.
for i in data:
  print(i)

print()
# 이건 range의 값을 출력해 보았다.
for i in range(1,10,1):
  print(i)

위 예제는 제가 제네레이터로 range함수와 똑같은 값이 나오게 정의한 것입니다. range함수도 1부터 시작해서 10까지 출력을 하고 1씩 증가하는 값을 가지고 있습니다.

제네레이터로 똑같은 결과가 나오게 구현했습니다.

제가 만들어 볼 예제가 딱히 없어서 range함수와 같은 결과가 나오는 제네레이터를 만들었습니다. 같은 결과가 나옵니다.

제네레이터를 어떤 상황일 때 사용할까 생각되는데 이게 컴프리헨션에서 사용됩니다.

링크 - [Python] 컴프리헨션(Comprehension)

list1 = list(range(1,10))

# list1의 값에서 짝수의 값을 list값으로 리턴했다.
listret = [x for x in list1 if x%2 == 0]
# list1의 값에서 짝수의 값을 제네레이터 값으로 리턴했다.
generator = (x for x in list1 if x%2 == 0)

for i in listret:
  print(i)

print()
for i in generator:
  print(i)

위 예제에서 결과는 같은 결과가 나옵니다. 그러나 메모리에 등록되어 있는 사이즈는 사이가 있습니다.

참조 - https://winterj.me/Python-Generator/

import sys
# 확실한 차이를 위해 1만까지 등록을 했습니다.
list1 = list(range(1,10000))

listret = [x for x in list1 if x%2 == 0]
generator = (x for x in list1 if x%2 == 0)

# 데이터 사이즈를 출력
print(sys.getsizeof(listret))
print(sys.getsizeof(generator))

이게 왜 이런 현상이 나타나면, list로 데이터를 넣은 경우는 list의 값을 전부 메모리에 갖고 있는 형태가 됩니다. 그러나 generator의 경우는 데이터를 메모리에 갖고 있는 형태가 아니라 구조적 오브젝트를 가지고 있는 상황이기 때문에 메모리가 늘어나지 않는 것입니다.(구조적 오브젝트는 함수를 가지고 있는 형태입니다.)


그럼 메모리의 효율을 많이 늘릴 수가 있습니다.

그러나 제네레이터도 단점이 있습니다. 그건 재사용이 불가능하다는 것입니다.

list1 = list(range(1,10))
generator = (x for x in list1 if x%2 == 0)

# 생성된 제네레이터를 루프를 두번 돌립니다.
for i in generator:
  print(i)
for i in generator:
  print(i)

결과는 한번에 generator만 나옵니다.


next함수를 사용하면 statck 알고리즘처럼 하나하나 값을 가져올 수 있습니다.

list1 = list(range(1,10))
generator = (x for x in list1 if x%2 == 0)

print(next(generator))
print(next(generator))
print(next(generator))
print(next(generator))
# 값은 총 4개가 있을 텐데 5번째 취득을 하면 어떻게 될까?
print(next(generator))

next함수로 2,4,6,8를 하나씩 출력했습니다. 마지막 다섯번째는 데이터가 없으니 에러가 발생하는 군요.


C#에서도 yield 문법이 있었는데 제가 이걸 왜 쓸까 싶었는데, Python으로 보니 왜 써야 하는지 알 것 같습니다.

또 한편은 최근 컴퓨터 사양이 너무 좋아져서 메모리는 거의 퍼다 써도 남는게 메모리인데.. 꼭 이렇게까지 만들 필요가 있을까하는 생각도 됩니다. 상황에 맞게 사용하면 꽤나 좋을 것 같기는 합니다.


여기까지 Python에서 제네레이터(Generator)에 대한 설명이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.