[TIL] # 17 iterator, generator

ddalkigum·2020년 12월 15일
1

TIL

목록 보기
17/50
post-thumbnail

list comprehension

사용법부터 보겠습니다

arr =[1, 2, 3]
numbers = [x for x in arr ]

>> numbers =[1, 2, 3]

numbers = [x for x in range(10)]

>> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

첫 느낌은 짧다

짧다는 건 강점이 될수도 있지만, 지나친 숏코딩은 가독성이 떨어지게 된다

하지만 list comprehension의 경우 짧은것 이외에도 강점이 하나 더있다

밑에는 보편적으로 많이 사용되는 리스트를 만드는 방법이다


arr =[]
for i in range(10):
	arr.append(i)
    

바로 위의 방식보다 걸리는 시간이 더 짧다

위에 보이는 시간이 일반적인 방식이 걸린 시간이고,
아래 보이는 시간이 list comprehension을 이용한 시간이다

함수를 호출해서 하는 방식은 이정도가 걸리는데 둘의 시간차이를 보면
2배 정도의 시간차이가 난다.

궁금한 점

만약 함수를 호출하지 않고 바로 적는 방법은 얼마나 걸릴까 궁금했다

first_start = timeit.default_timer()
result = []
for i in range(1000):
    result.append(i)
first_end = timeit.default_timer()
print(f"first_timer = {(first_end-first_start)*1000}")

second_start = timeit.default_timer()
numbers = [ i for i in range(1000)]
second_end = timeit.default_timer()
print(f"second_timer = {(second_end-second_start)*1000}")

이 두가지를 가지고 실험을 했는데 ,
결과는 list comprehension이 더 빠른걸로 나왓다
근데 여기서 함수를 불럿을때 처럼 거의 2배가량 차이 나는게 아닌
비슷한 경우도 있엇다

평균적으로 0.06 밀리세컨드 정도의 차이가 났다

혹시나 위에서 측정했던 방법이랑 달라서 차이가 나는건가 싶어서

모두 같은 방법으로 시간을 측정 해봣다

함수로 부르게 되면 시간이 더 단축된다??

어떤 것이 더 빠르다 단정짓기가 어려운 문제인 것 같다
속도에 관한 글들을 찾아보니
어떤분은 함수로 호출할 경우가 더 빠르다 하고
또 어떤분은 바로 실행한 결과가 더 빠르다고 한다
아마 케바케이지 않을까 싶다


iterator, generator

이제 제대로 이 두녀석에 대해 알아보겟습니다

둘의 가장 큰 차이점은 저장하느냐 안하느냐 입니다

iterator > generator

이터레이터 안에 제너레이터가 포함되어 있는 것입니다
generator를 통해 iterator를 만든다,
혹은 iterator중에서도 특수한 경우에 generator를 사용한다
이 정도가 될 수 있습니다

Iterator

우선 itertor 저장을한다 안한다로 나누었는데
iterator의 경우는 저장을 해놓고 하나씩 출력을 하는 방식입니다.

가장 기본적인 구조부터 보면

arr = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

iterator = iter(arr)

print(next(iterator))
print(next(iterator))
print(next(iterator))

위의 iterator의 경우는 next함수를 이용해서 값을 하나씩 출력하는 방법입니다
결과는 next를 입력했던 숫자만큼 출력이 되는 걸 확인할 수 있습니다.

같은 방법이긴 하나 아까랑은 조금다른
iterator를 메서드를 이용해서 사용하는 걸 보겠습니다.

사용법은 거의 비슷합니다
우선 코드부터 보면

arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

iterator = arr.__iter__()
count = 0
while count < 10:
    print(iterator.__next__())
    count += 1

위의 코드를 보게 되면 arr의 내부의 값을 출력하게 되는데
next 메서드를 이용해서 안의 값을 하나씩 출력하게 만들어 주는 코드입니다

결과를 보면

이렇게 정상적으로 출력이 되는걸 확인 할 수 있습니다.
저장이 된다 안된다는 마지막 generator까지 본다면 이해가 되실겁니다

iterator?, iterable?

모든 iterator는 iterable이 될 수 있지만,
모든 iterable이 iterator가 될 수는 없다

iterator = 반복 가능한 객체

일반 리스트와 비슷해 보이는데 다른점은?

while count<10 -> while count<11

일반적으로 리스트나, 튜플등 자기가 가지고 있는 범위를 벗어나는
범위를 설정하게 되면 IndexError가 발생합니다

근데 여기서 iterator의 경우는 다릅니다

이렇게 StopIteration 에러가 발생하며 종료하게 됩니다

두번째

우선 밑에 부터 보시죠

python에서 list의 dir입니다

보시면 iterator에서 소개한 next 메서드가 없는걸 확인 할 수 있는데
이는 list 뿐 아니라, set, dict, tuple, string 등... 반복가능한
iterable 객체인 것입니다

iter( list ) 를 사용하여 iterator 객체로 만들어 줄 수 있습니다

Generator

제너레이트의 경우는 저장을 하고 결과를 내놓는게 아닌
필요에 따라서 하나씩 결과물을 나오게 합니다

바로 yield를 사용해서 실행할 코드를 보내주는 겁니다

제너레이트의 경우는 인삿말을 붙이는 코드로 예를 들겠습니다

def hello(students):
    for i in students:
        yield (f"{i} Hello!")
        

student = ['junsik','minwoo','jiyoung','soheon']

hi = hello(student)

print(hi.__next__())
print(hi.__next__())

만약 이렇게 하게 된다면 어떻게 될까요?

list comprehension을 사용한 식은 바로 리스트를 출력해서 보여주고
generator expression을 사용한 식은 제너레이터 오브젝트로 출력을 합니다

iterator와 마찬가지로 next()메서드를 사용해서 호출해줍니다

Generator Expression

제너레이터 표현식인데,
list comprehension과 사용법은 유사합니다

iterator = [i for i in range(10)]	# list comprehension

generator = (i for i in range(10))	# generator expression

이와 같이 [] 대괄호가 아닌 () 소괄호를 사용해서
만들어 준다면 generator가 되는 겁니다

list comprehension과 generator expression의 차이점을 보면

이렇게 하나는 모두 계산된 상태로 출력을 해주고
다른 하나는 제너레이터 객체를 반환해 줍니다

Lazy evaluation

모든 걸 저장해놓고 하나씩 출력하는게 아닌
필요에 의해 계산, 출력등의 행동을 보여주는데,
이를 Lazy evaluation 라고 합니다
실행을 지연시킨다는 뜻으로 generator가 사용되는 이유입니다

list comprehension 과 generator expression의 차이점

이제 제대로 둘의 차이점을 살펴 볼게요

generator

import time

count = 1

def timer():
    print("please wait for 1 second")
    time.sleep(1)
    print("return 1")
    return 1

generator_list = (timer() for k in range(3))

for j in generator_list:
    count -=1
    print(j)
    if count ==0:
        break

우선 제너레이터 부터 살펴보면 계산을 전부 하지않고
그때그때 저장한다고 했는데
실행결과를 보게 되면

1개를 실행 시킨후 출력하는 걸 볼수 있습니다

다음은 list comprehension을 볼게요

import time

count = 1

def timer():
    print("please wait for 1 second")
    time.sleep(1)
    print("return 1")
    return 1

comprehension_list = [timer() for k in range(3)]

print("start comprehension")
for j in comprehension_list:
    count -=1
    print(j)
    if count ==0:
        break

실행 결과를 볼게요

우선 계산을 끝내고 난뒤에 밑에 for문을 실행시킨걸 확인 할 수 있습니다

이렇게 보면 확 와닿을 텐데
미리 계산할 결과를 저장해놓은 후에 하나씩 출력하는것과
미리 계산을 하지 않고 하나씩 출력하는걸 볼 수 있습니다

list comprehension의 경우는 미리 계산을 하기 때문에
계산한 만큼의 메모리를 할당 해놓았습니다
반면에 generator의 경우 계산을 하지 않은 만큼의 메모리 절약이 있습니다

둘의 차이점이 확실하게 보이는 결과인데

상황마다 다르겟지만 처리해야할 일이 많은데
모든걸 계산해놓고 출력을 하게되면 연산이 커질수록 그만큼
메모리를 할당하게 되고, 불필요한 메모리 할당이 이루어 지는겁니다

두가지의 차이점을 알아봤는데,
두가지 모두 사용하는 곳이 다르기 때문에 상황에 맞게
사용하시면 될것 같습니다

도움받은 곳

http://pythonstudy.xyz/python/article/23-Iterator%EC%99%80-Generator

https://itholic.github.io/python-lazy-evaluation/

두 곳모두 예시가 너무 좋아서 이해하는데
도움이 많이 됬습니다 감사합니다 😁

profile
딸기검 -본캐🐒 , 김준형 - 현실 본캐 🐒

0개의 댓글