python 16장. 이터레이터&제너레이터&코루틴

Hyuna·2024년 8월 4일

Python 기본

목록 보기
16/17
post-thumbnail

1. 이터레이터(Iterator)

반복 가능한 객체 요소를 순차적으로 꺼낼 수 있는 객체

  • 이터레이터를 생성하고 값이 필요한 시점이 되었을 때 값을 만드는 지연평가 방식 사용
  • '반복자'라고도 한다
__iter__ : 객체가 반복 가능한 객체인지 알아보는 메서드

__next__: 요소를 차례대로 꺼낼 수 있는 메서드
          꺼낼 요소가 없으면 Stopiteration 예외 발생시켜 반복 종료
it = [1,2,3].__iter__()
print(it.__next__())
print(it.__next__())
print(it.__next__())
print(it.__next__())

📍 리스트,문자열,딕셔녀리,세트 등 반복 가능한 객체(iterable)는 iter를 호출해야 이터레이터(iterrator)로 사용 가능 📍


📌 이터레이터 만들기


✔ iter(반복자)
  • 반복을 끝낼 값을 지정하면 특정 값이 나올 때 반복 종료
  • sentinel(감시병): 반복을 끝낼 값
iter(호출가능한 객체, 반복을 끝낼 값)

#iter함수를 활용하면 if조건문으로 매번 숫자가 2인지 검사하지 않아도 된다

import random

while True:
    i = random.randint(0,5)
    if i == 2:
        break
    print(i, end=' ')
    
    
# for문과 iter 사용
import random
for i in iter(lambda : random.randint(0,5),2): #2를 생성할 때 종료, 2는 출력 x
    print(i, end=' ')



📍 __iter__ 와 iter()의 차이 📍

'__iter__' 
* 클래스 내에 구현되는 메서드
* 보통 클래스의 인스턴스 메서드로 정의
 
iter()
* 내장 함수
* 인자로 전달된 객체의 '__iter__' 메서드를 호출하여 이터레이터 반환

# __iter__ 예시

class MyIterable:
    def __init__(self,data)
        self.index = 0
        self.data = data
      
    def __iter__(self):
        return self

# iter() 예시
my_list = [1,2,3]
iterator = iter(my_list)

print(next(iterator)
print(next(iterator)
print(next(iterator)
# 다음 호출에서는 StopIteration 예외 발생

✔ next()
  • 기본값 지정 가능
  • 기본값을 지정하면 반복이 끝나더라도 Stopiteration이 발생하지 않고 기본값 출력

it = iter(range(3))

print(next(it, 10))
print(next(it, 10))
print(next(it, 10))
print(next(it, 10))


#클래스로 이터레이터 작성 
class Counter: 
    def __init__(self, stop):
        self.current = 0 #현재 숫자 유지, 0부터 지정된 숫자 직전까지 반복
        self.stop = stop #반복을 끝낼 숫자
        
    def __iter__(self):   #현재 인스턴스 반환
        return self
    
    def __next__(self):
        if self.current < self.stop: # 현재 숫자가 번복을 끝낼 숫자보다 작을 때
            r = self.current #반환할 숫자 변수에 저장
            self.current += 1 #현재 숫자를 1증가
            return r #숫자를 반환
        else: #현재 숫자가 반복을 끝낼 숫자보다 크거나 같을 때
            raise StopIteration #예외발생
        
        
for i in Counter(3):
    print(i, end=' ')


✔ getitem()
  • 인덱스로 접근하기

class MyIterator:
      def __init__(self, data):
            self.data = data
            self.index = 0
            
      def __iter__(self):
            return self
            
      def __next__(self):
            if self.index >= len(self.data):
                 raise StopIteration
           value = self.data[self.index]
           self.index += 1
           return value
           
      def __getitem__(self, index):
                 if index < 0 or index >= len(self.data):
                       raise IndexError("Index out of range")
                 return self.data[index]
 

numbers = [1, 2, 3, 4, 5]
iterator = MyIterator(numbers)
 
for num in iterator:
      print(num)

# 인덱스로 접근하여 값 출력
print("Index access:")
print(iterator[0])    >> 1
print(iterator[3])    >> 4
✔ 언패킹
  • Counter()의 결과를 변수 여러개에 할당 할 수 있다
  • 이터레이터가 반복하는 횟수 = 변수 개수

a,b,c = Counter(3)
print(a,b,c)

a,b,c,d,e = Counter(5)
print(a,b,c,d,e)


제너레이터(generator)

이터레이터를 생성해주는 함수(발생자)


2. 제너레이터

✔yield
  • 간단하게 이터레이터 구현 가능

def number_gen():
    yield 0
    yield 1
    yield 2
    
for i in number_gen():
    print(i)




def num_gen(stop):
    n = 0
    while n < stop:
        yield n
        n += 1
        

    
g = num_gen(3)
print(next(g))
print(next(g))
print(next(g))
print(next(g))


  • 현재 함수를 잠시 중단하고 함수 바깥의 코드가 실행되도록 전달

def upper_gen(x):
    for i in x:
        yield i.upper()
        
f = ['apple','pear','grape','pineapple','orange']
for i in upper_gen(f):
    print(i)


  • yield from: 값을 여러번 바깥으로 전달

def num_gen():
    x = [1, 2, 3]
    yield from x 
    
    
for i in num_gen():
    print(i)



3. 코루틴(coroutin)

메인루틴과 서브루틴과 cooperative routine을 가지는 서로 협력하는 루틴

  • 일반적으로 메인 루틴에서 서브루틴을 호출하면 서브 루틴의 코드를 실행한 후 다시 메인루틴으로 돌아간다
  • 서브 루틴이 끝나면 서브 루틴은 내용은 사라진다
  • 서브 루틴은 메인 루틴에 종속적
def add(a,b):
    c = a+b
    print(c)
    print('add함수')
    
    
def calc():
    add(1,2)
    print('clac함수')

calc()



>> calc() 메인 루틴 실행 후 add() 서브루틴 실행

* 코루틴은 메인 루틴과 서브 루틴이 서로 대등한 관계로 특정 시점에서 상대방의 코드를 실행

  • 코루틴은 여러 번 실행 가능
  • 함수가 종료되지 않은 상태에서 메인 루틴 코드를 실행 후 다시 돌아와 코루틴 코드 실행

📌 코루틴에 값 보내기

  • 코루틴은 제너레이터의 특별한 형태
코루틴 객체.send()
변수 = yield



def number_coroutine():
    while True: #코루틴을 유지하기 위해 무한루프 사용
        x = (yield)
        print(x)
       
co = number_coroutine()
next(co)    #코루틴 안의 yield 코드 실행

co.send(1)  #코루틴에 숫자를 보냄
co.send(2)
co.send(3)

✔ 바깥으로 값 전달하기
변수 = (yield 변수)
변수 = next(코루틴객체)
변수 = 코루틴객체.send()




def sum_co():
    total = 0
    while True:
        x = (yield total) #코루틴 바깥에서 값을 받아와 바깥으로 값 전달
        total += x
        
        
        
co = sum_co()
print(next(co)) #코루틴 안의 yield까지 코드를 실행하고 코루틴에서 나온 값 출력

print(co.send(1)) #코루틴에 숫자를 보내고 코루틴에서 나온 값 출력
print(co.send(2))
print(co.send(3))


📍 제너레이터/코루틴 차이 📍
* 제너레이터는 next를 반복호출하여 값을 얻어내는 방식
* 코루틴은 next를 한번만 호출한 뒤 send로 값을 주고받는 형식

✔ 코루틴 종료하고 예외 처리하기
  • 코루틴은 실행 상태를 유지하기 위해 끝나지 않는무한 루프 동작(while: True)
코루틴.close()


def number_co():
    while True:
        x = (yield)
        print(x, end =' ')
        
co = number_co()
next(co)

for i in range(20):
    co.send(i)
    
co.close()

  • 코루텐 객체에서 close 메서드 호출시 코루틴이 종료될 때 GeneratorExit 예외 발생

def number_co():
    try:
        while True:
            x = (yield)
            print(x, end =' ')
    except GeneratorExit:
        print()
        print('코루틴 종료')
        
co = number_co()
next(co)

for i in range(20):
    co.send(i)
    
co.close()



✔ 코루틴 안에서 예외 발생시키기
  • 코루텐 안에서 예외를 발생시킬 때 throw 메서드 사용
  • 예외를 코루틴 안으로 던짐
  • throw 메서드에 지정한 에러 메시지는 except as 변수로 들어간다
코루틴객체.throw(예외이름, 에러 메시지)



def sum_co():
    try:
        total = 0
        while True:
            x = (yield)
            total += x
    except RuntimeError as e: #중간에 예외 발생시키기
        print(e)
        yield total
        
    
co = sum_co()
next(co)

for i in range(20):
    co.send(i)
    
print(co.throw(RuntimeError, "예외를 코루틴 끝내기"))



✔ 하위 코루틴 반환값 가져오기
변수 = yield from 코루틴()
** yield from은 python 3.3 이상부터 사용가능



def accumulate():
    total = 0
    while True:
        x = (yield)
        if x is None:
            return total
        total += x
    
def sum_co():
    while True:
        total = yield from accumulate()  # accumulate 반환값을 가져옴
        print(total)
        
co = sum_co()
next(co)

for i in range(1, 11): #1~10까지 반복
    co.send(i) # 코루틴 accumulate에 숫자를 보냄
co.send(None)  코루틴 accumulate에 None을 보내서 숫자 누적 끝냄

for i in range(1, 101):
    co.send(i)
co.send(None)



💡 클래스를 이용해 이터레이터를 만들고 리스트 요소를 출력해보자

>> numbers = [1, 2, 3, 4, 5]
# 클래스 없이 구현할 때
numbers = [1, 2, 3, 4, 5]
iterator = iter(numbers)

while True:
    try:
        result = next(iterator)
        print(result)
        
    except StopIteration:
        break

# 클래스로 구현할 때

class Iterator():
    def __init__(self, stop):
        self.current = 0
        self.stop = stop
        
    def __iter__(self):
        return self
    
    def __next__(self):
        
        if self.current < len(self.stop):
            r = self.stop[self.current]
            self.current += 1
            return r
        else:
            raise StopIteration
            
numbers = [1, 2, 3, 4, 5]
iterator = Iterator(numbers)

for i in iterator:
    print(i, end=' ')


💡 위의 리스트를 인덱스로 접근해보자

>> 인덱스 접근은 getitem 사용


class Iterator():
    def __init__(self, stop):
        self.index = 0
        self.stop = stop
        
    def __iter__(self):
        return self
    
    def __next__(self):
        
        if self.index < len(self.stop):
            r = self.stop[self.index]
            self.index += 1
            return r
        else:
            raise StopIteration
        
    def __getitem__(self,index):
        if 0 <= index < len(self.stop):
            return self.stop[index]
        else:
            raise StopIteration
            
            
numbers = [1, 2, 3, 4, 5]
iterator = Iterator(numbers)

print(iterator[0])
print(iterator[4])



💡 파보나치 수열 숫자를 생성하는 이터레이터를 만들어보자

>> 첫번째 숫자 = 0, 두번째 숫자 = 1
>> 수열이 100을 넘어가면 종료
class Fib():
    def __init__(self):
        self.a = 0
        self.b = 1
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.a > 100:
            raise StopIteration
        
        current = self.a
        self.a, self.b = self.b, self.a + self.b
        return current
    
    

fib_iter = Fib()

for number in fib_iter:
    print(number)  


💡 한 번에 한 줄씩 읽어오면서 파일의 내용을 처리하는 제너레이터를 작성해보자

def read_lines(filename):
    with open(filename, 'r') as file:
        while line := file.readline(): 
            yield line.strip()

filename = 'show.txt'
with open(filename, 'w') as f:
    f.write("First line\nSecond line\nThird line")

for line in read_lines(filename):
    print(line)

0개의 댓글