# 22. PYTHON 심화(2)

김광일·2022년 2월 9일
0

PYTHON

목록 보기
10/13
post-thumbnail

오늘은 위코드 2주차 3일째! iteratorgenerators에 대해 정리해보려한다.


1. ITERATOR

list, set, dictionary ... 와 같은 객체들은 for문을 써서 데이터를 하나씩 처리할 수 있는데, 이렇게 데이터를 하나하나 처리할 수 있는 반복 가능한 객체들을 iterable object라고 한다.
그리고 iterable object 임을 확인할 수 있는 방법은 dir로 호출하여 __iter__ 함수가 있는지 보면 된다.

a = [1, 2, 3]  # iterable 객체

print(dir(a))
>['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', 
'__gt__','__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__',
'__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', 
'__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', 
'__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort']

3번째 줄 마지막에 __iter__ 함수가 있음을 확인할 수 있고, 이를 직접 사용하여 출력해보면 iterator 객체임을 확인할 수 있다.

a = [1, 2, 3]
print(a.__iter__()) # = print(iter(a))
>  <list_iterator object at 0x1030021d0>
  • 여기서 a를 튜플이나 세트, 딕셔너리 형태로 바꾸게 되면 list 부분이 tuple, set, dict_key의 형태로 바뀌어 출력된다.
b = (1, 2, 3)
c = {1, 2, 3)
d = {1:"one", 2:"two", 3:"three"}

print(b.__iter__()) 
print(c.__iter__()) 
print(d.__iter__()) 
>  <tuple_iterator object at 0x1027b0190>
<set_iterator object at 0x109bb4280>
<dict_keyiterator object at 0x10e6e1710>

다시 본론으로 와서 iterator_adir로 찍어보게되면 __next__라는 함수를 발견할 수 있다.
이 함수는 이름에서 유추할 수 있듯이 다음 요소를 하나씩 꺼내오는 함수이다.
현재 a 안에는 3개의 값이 들어있으므로 이 __next__ 함수를 3번 호출하게 되면 1, 2, 3이 출력된다.
여기서 한 번 더 호출을 하게 되면 StopIteration 이 발생하게 된다. 이는 다음 존재하는 값이 없기 문에 말 그대로 iter에 대한 명령을 멈춘다는 뜻이다.    iter 멈춰!

print(iterator_a.__next__())  # = print(next(iterator_a))
print(iterator_a.__next__())
print(iterator_a.__next__())
# print(iterator_a.__next__())
>1
2
3
#StopIteration

이제 while문을 이용하여 a 를 제곱한 코드를 구현해보자.

a = [1, 2, 3]
iterator_a = iter(a)

while True:
	try:
    	next_a = next(iterator_a)
	except StopIteration:
    	break
	print(next_a ** 2)
> 1
4
9

이번엔 리스트가 아닌 딕셔너리에 대한 for문iter를 사용하여 while문으로 바꿔보자.

D = {'a':1, 'b':2, 'c':3}
#for key in D.keys():
#    print(key)
# > a
# b
# c

while True:
	try:
    	iterator_D = iter(D)
        next_D = next(iterator_D)
	except StopIteration:
    	break
	print(next_D)
> a
b
c

2. GENERATORS

보통 파이썬 내에서 함수는 그 값을 반환하고 종료한다. 하지만 generator 함수는 값을 반환하기는 하지만 산출 (yeild) 한다는 차이점이 있다. 이 generatoriterator를 생성해주는 함수라고 볼 수 있다.

generatoriterable한 순서가 지정되기에 generatoriterator라고 볼 수 있다.
또한 이 generatordir함수로 찍어보게 되면 iterator와는 다르게 __iter____next__함수가 둘 다 들어있음을 확인할 수 있다. 때문에 __iter__를 먼저 호출하지 않아도 __next__함수를 바로 호출할 수 있다.
예시를 보자.

def generator_number():
	for i in range(4):
    	yield i
        
print(generator_number())
> <generator object generator_squares at 0x10f5d69d0>

gen = generator_number()
print(next(gen))  # 0 
print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3
# print(next(gen))  # StopIteration
print(type(gen)) # generator

iterator와 마찬가지로 정해진 값을 넘어서면 StopIteration이 발생한다는 것을 확인할 수 있다.

여기서 갑자기 궁금한 것이 생겼다. generator_number()함수를 gen 에 따로 담지 않고 그냥 돌리게 되면 어떻게 될까? 그저 길이가 길어져서 이렇게 하는 걸까?

print(next(generator_number())) # 0
print(next(generator_number())) # 0
print(next(generator_number())) # 0
print(type(generator_number())) # generator

몇 번을 넣어봐도 값은 0. 즉 첫 값이 나온다. 분명 type을 찍어봤을 땐 둘 다 generator인데..
왜 그런가 알아보니 아래와 같은 방법으로 했을 경우엔 세 개의 generator_number()가 각각 다른 generator로 인식이 된다는 것이다. 이는 위에서 했던 iterator에서도 마찬가지였다.        알다가도 모를 파이썬 세계..

iterable_a = [1, 2, 3]
print(next(iter(iterabl_a))) # 1
print(next(iter(iterabl_a))) # 1
print(next(iter(iterabl_a))) # 1
print(type(iter(iterabl_a))) # list_iterator

그리고 이 generator의 표현식은 Lazy Evaluation 을 위해서 사용될 수 있다.
여기서 Lazy Evaluation란 말 그대로 실행을 지연시킨다는 의미인데, 어떤 값이 실제로 쓰일 때까지 그 값의 계산을 뒤로 미루는 동작 방식이다.

Lazy Evaluation를 이해하기에 좋은 블로그이다.
https://itholic.github.io/python-lazy-evaluation/
https://bluese05.tistory.com/56

이 블로그에서 사용한 예시를 하나 가져와봤다.
아래 함수는 1초간 print를 수행한 후 x 값을 return하는 함수이다. 그 때 listgenerator를 생성했을 때 차이를 볼 것이다. (generator expressionlist comprehension과 똑같이 사용할 수 있는데 이 때 차이점은 [ ] 대괄호로 묶는 것이 아니라 ( ) 소괄호로 묶는다는 점이다.)

def isay_func(x):
	print "I say.."
  	time.sleep(1)
	return x

#list생성
isay_list = [isay_func(x) for x in range(3)]
for i in isay_list:
	print(i)
>I say...
I say...
I say...
0
1
2

#generator 생성 
isay_generator = (isay_func(x) for x in range(3))
for i in isay_generator:
	print(i)
>I say...
0
I say...
1
I say...
2

결과를 보면 list의 경우에는 I say... 가 1초 간격으로 출력이 된 후0,1,2 값이 출력이 됐다.
결과 값에서도 다르게 보이는 generator의 경우엔 I say...0을 출력 후 1초 간격으로 I say... 1, I say... 2를 출력함을 볼 수 있다.
이는 코드가 더 길어질 경우 시간과 메모리를 절약할 수 있다.


느낀점

내용은 이해했지만 아직 실제로 나의 공부 과정에 적용하기엔 어려움이 있다.
좀 더 공부하고, 더 많은 예시를 보면서 나에게도 적용시킬 수 있도록 노력해야겠다.

profile
부족함 없이 공부하자

0개의 댓글