파이썬에는 원소를 받을 수 있는 다양한 자료구조가 있다. 특히 리스트, 딕셔너리 등은 여러 원소를 처리하는 대표적인 자료구조이다.
원소는 기본적으로 정적으로 만들어져서 반복 가능한 반복형(Iterable) 클래스이다. Iterable하다는 뜻은 반복 가능한 객체란 뜻이다. 이런 반복형 클래스를 동적 객체로 변환해 반복자로 만들고, 필요할 때마다 원소를 추출해서 처리하는 방식이 반복자(Iterator) 클래스이다. 즉, iterator란 반복 가능한 객체(리스트, 퓨플, 셋, 사전, 문자열)에서 반복문을 활용하여 데이터를 순회하면서 각 요소를 하나씩 꺼내 어떤 처리를 수행할 수 있도록 하는 간편한 방법을 제공하는 객체이다.
Iterable과 Iterator는 ‘collections.abc’ 내장 모듈에 정의되어 있는 추상 클래스로, 두 클래스는 python의 Iterator Protocol이라는 개념 속에서 정의되는 한 쌍이다.
파이썬에서는 반복 가능한 타입(Iterable은 순회할 수 있는 모든 객체)
- Collection
- Text file
- List
- Dict
- Set
- Tuple
- Unpacking
- *args
위의 나열된 순회 가능한 Sequence 객체들에서 반복 가능한 타입은 반복 가능한 객체(Iterable)하다는 뜻이다.
첫 번째 예제이다.
tuple_a = (1, 2, 3)
iterator_ex = iter(tuple_a) # tuple -> iterable -> iterator 반환
print(iterator_ex.__next__())
print(iterator_ex.__next__())
print(iterator_ex.__next__())
print(iterator_ex.__next__())
1. 튜플 선언
2. iter 객체 반환
3. 3번째 print 까지는 next() 메서드 호출 후 내부 데이터 순회
4. 4번째 print 시 StopIteration 예외 발생
# 반복 가능한 이유? -> iter(x) 함수 호출
t = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
# for 반복
for c in t:
print(c)
print(dir(t))
>>> '__iter__'
예를 들어 위의 예제는 문자열로 반복가능(Iterable)하다. print(dir(t)) 를 하게 되면, 매직 메서드인 __iter__가 존재하는 것을 알 수 있다. 반복가능하다는 것은 Iterator를 통해서 Iterable 하게 만들 수 있다.
for 반복을 내부적으로 설명하자면 문자열 t가 iter(x) 함수를 호출하여 Iterable 한 것을 받아서 next() 함수를 통해서 나오게 되는 것이다.
w = iter(t)
print(dir(w))
>>> __next__
print(next(w))
>>> A
print(next(w))
>>> B
print(next(w))
>>> C
이것을 증명하기 위해 iter함수의 dir을 살펴보면 __next__ 가 존재하는 것을 알 수 있다. 이를 통해 next()를 호출하면 다음에 올 순서를 기억하고 있다.
while True:
try:
print(next(w))
except StopIteration:
break
이를 이용하여 While로 똑같이 for 반복과 똑같이 할 수 있다.
반복형 확인
from collections import abc
# 반복형 확인
print(hasattr(t, '__iter__'))
print(isinstance(t, abc.Iterable))
반복형을 확인하려면 다음과 같은 방법이 있다.
1. 객체에 Attribute 가 있는지 확인하는 방법
2. 추상클래스를 관리하는 collections.abc 모듈을 import 한다. 반복형 추상 클래스는 Iterable이다. 이 클래스를 상속해 구현된 메서드가 있다. 이를 통해 추상 클래스를 상속했는지 알아보려면 isinstance()를 통해 확인할 수 있다.
3. dir 을 통해 __iter__ 있는지 확인
단어를 분리하는 단어 스플리터 만들기
# next 사용
class WordSplitter:
def __init__(self, text):
self._idx = 0
self._text = text.split(' ')
def __next__(self):
# print('Called __next__')
try:
word = self._text[self._idx]
except IndexError:
raise StopIteration('Stopped Iteration.')
self._idx += 1
return word
def __repr__(self):
return 'WordSplit(%s)' % (self._text)
wi = WordSplitIter('Do today what you could do tomorrow')
print(wi)
>>> WordSplit(['Do', 'today', 'what', 'you', 'could', 'do', 'tomorrow'])
1. __init__ 을 통해 idx와 text는 공백을 기준으로 초기화
2. __next__ 를 통해 현재 idx에 해당하는 값을 내보내고 idx 에러가 날 때까지 StopIteration을 하여 예외처리를 하고 idx를 늘려준다.
3. __repr__ 를 통해 어떤 클래스인지 알려준다
이를 통해 클래스이지만 Iterable 하게 사용할 수 있습니다.
제네레이터(Generator)의 이해
제너레이터
iterator의 한 종류로, 하나의 요소를 꺼내려고 할 때마다 요소 generator를 수행하는 타입으로, Python에서는 yield문을 통해 구현하는 것이 보통이다. Generator는 나만의 Iterable, Iterator 기능을 만들되, 생성 문법을 기존보다 단순화한 개념 또는 클래스라고 할 수 있다. 리스트나 Set과 같은 컬렉션에 대한 iterator는 해당 컬렉션이 이미 모든 값을 가지고 있는 경우이나, Generator는 모든 데이터를 갖지 않은 상태에서 yield에 의해 다음의 return 반환될 위치 값을 기억하고 하나씩만 데이터를 만들어 가져온다는 차이가 있다. 미리 만들어 놓지 않기 떄문에 그때그때 처리하는 것이 좋은 경우 사용된다.
이에 대한 의미는 제너레이터는 작은 메모리 조각으로도 연속되는 데이터를 만들어낼 수 있다는 의미이다. 예를 들어 [1, 2, 3, ... , 1000000] 까지 있다면 매우 많은 메모리를 할당하게 된다. 많은 메모리를 할당해서 비효율이 발생하지만 제너레이터는 다음에 내가 반환할 값만 가지고 있어서 1이 반환된다면 2에 대한 위치정보를 가지고 있고 메모리는 갖고있지 않아 메모리를 적게 사용한다. 정리하자면 제너레이터는 연속되는 값을 만들어내는데 메모리 사용량을 적게 사용한다는 의미이다. 한 번에 한 개의 항목을 생성하여 메모리 유지를 하지 않습니다.
제너레이터 사용 이유
1. 지능형 리스트, 딕셔너리, 집합 등은 데이터 양이 증가할 수록 메모리 사용량이 증가합니다. 이를 위해 제너레이터 사용이 권장한다.
2. 단위 실행 가능한 코루틴 구현과 연동
3. 작은 메모리 조각 사용
제너레이터(Generator)
chars = '!@#$%^'
tuple_g = (ord(s) for s in chars)
print(tuple_g) # <generator object <genexpr> at 0x00000284FD68F660>
print(next(tuple_g))
print를 하면 generator 가 나오게 되는데 값을 생성하지 않은 상태이고 다음 상태 값을 반환할 준비만 한다는 의미이다. 제너레이터는 next()를 이용하여 다음 값을 반환할 수 있다. 끝에 Generator 할 것이 없다면 Stop iterator가 발생하여 끝나게 된다.
# 제너레이터 예제
print(('%s' %c + str(n) for c in ['A', 'B', 'C', 'D']) for n in range(1, 21))
for s in ('%s' %c + str(n) for c in ['A', 'B', 'C', 'D'] for n in range(1, 21)):
print(s)
단어를 분리하는 단어 스플리터 만들기(제너레이터 이용)
class WordSplitGenerator:
def __init__(self, text):
self._text = text.split(' ')
def __iter__(self):
# print('Called __iter__')
for word in self._text:
yield word # 제네레이터
return
def __repr__(self):
return 'WordSplit(%s)' % (self._text)
wg = WordSplitGenerator('Do today what you could do tomorrow')
wt = iter(wg)
1. next를 이용할 때는 idx를 기억해야했지만 제너레이터는 필요가 없다.
2. next를 구현할 때는 __iter__ 함수를 구현하면 끝이난다. for 문을 사용한다.
3. yield 키워드를 사용한다. yield는 다음의 return 반환될 위치 값을 기억한다.
'Language > Python' 카테고리의 다른 글
[Python] 네임스페이스(namespace)에 대한 이해 (0) | 2021.12.08 |
---|---|
[Python] 제네레이터(Generator)를 이용한 itertools 예시 (0) | 2021.12.01 |
[Python] 데코레이터(Decorator) (0) | 2021.11.17 |
[Python] Scope에 대한 이해 (0) | 2021.11.10 |
[Python] 네임드 튜플(Named Tuple) (0) | 2021.10.26 |
댓글