본문 바로가기

Back-End/Python

[Python] 파이썬 알고리즘 - 이터레이터(Iterator), readLine(), nextLine()

~ 목차 ~

이터레이터(Iterator)

  • 파이썬에서 반복 가능한 객체(클래스)를 표현하는 데 사용되는 인터페이스
  • __iter__()  : 이터레이터 객체 자체를 반환
  • __next__()  :  다음 요소를 반환, 더 이상 반환할 요소가 없으면 raise StopIteration 예외를 발생시켜 순회를 종료
  • 이터레이터는 init() 함수와 next() 함수를 이용하여 반복(Iterator)을 수행함
  • 이터레이터 기능은 반복문(for or while)을 사용해야만 작동하는 기능
    최초 __iter__() 함수를 호출하고, 출력 시 __next__() 함수가 한번씩 수행하면서 값을 반환받아서 출력함
    한번 반환된 후 메모리는 초기화되며, 다음 반복시 다시 메모리 사용
  • 메모리를 효율적으로 활용할 수 있음
  • java의 readLine()이랑 nextLine()와 비슷!! 

 

< 이터레이터 기본 구조>

### 클래스 정의하기
class MyIterator:

    ### 클래스 생성자 정의하기
    def __init__(self, start, end):
        self.current = start
        self.end = end
        
    ### 자신의 클래스를 반환하는 iter 함수 정의
    def __iter__(self):
        return self

    ### 반복을 수행하는 next 함수 정의
    def __next__(self):
        if self.current <= self.end:
            result = self.current
            self.current += 1
            return result
        else:
            raise StopIteration

# 이터레이터 객체 생성
my_iterator = MyIterator(1, 5)

# 이터레이터 순회
for item in my_iterator:
    print(item)

※ 이터레이터는 반복이 끝나면 종료시켜야함
    종료시키는 방법은 강제로 오류 발생시킴 : raise StopIteration 

 


< 이터레이터 실습1>

 

<예제> 0부터 4까지의 숫자를 생성해서 반환하는 이터레이터 만들기

class MyIterator:
    
    def __init__(self):
        self.current_value = 0
        print(f"#1(__init__) : self={self} / self.current_value = {self.current_value}")

    def __iter__(self):
        print(f"#2(__iter__) : self={self}")
        return self
        
    def __next__(self):
        print(f"#3(__next__) : self={self}")
        ### current_value의 값이 5보다 작을 때까지 반복 수행
        if self.current_value < 5:
            # 반환할 변수에 current_value의 현재값 저장
            result = self.current_value
            # current_value의 현재값을 1증가
            self.current_value += 1
            
            print(f"#4(__next__) : result={result} / self.current_value = {self.current_value}")
            
            # result 값 반환 (최초는 0)
            return result
        else:
            print("#5(else) : StopIteration 예외 발생!!")
            raise StopIteration

 

<이터레이터 실행시키기>

1번 방법 반복하여 전체 실행하기
 - 클래스 생성하기

my_iterator = MyIterator()

결과

#1(__init__) : self=<__main__.MyIterator object at 0x0000028D8FC2BFD0> / self.current_value = 0

 


반복 수행하여 result 값 출력하기

for value in my_iterator:
    print(value)

결과

#2(__iter__) : self=<__main__.MyIterator object at 0x0000028D8FC2BFD0>
#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8FC2BFD0>
#4(__next__) : result=0 / self.current_value = 1
0
#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8FC2BFD0>
#4(__next__) : result=1 / self.current_value = 2
1
#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8FC2BFD0>
#4(__next__) : result=2 / self.current_value = 3
2
#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8FC2BFD0>
#4(__next__) : result=3 / self.current_value = 4
3
#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8FC2BFD0>
#4(__next__) : result=4 / self.current_value = 5
4
#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8FC2BFD0>
#5(else) : StopIteration 예외 발생!!


<이터레이터 실행시키기>

2번 방법 한 건, 한 건 실행하기

 - 클래스 생성하기

my_iterator = MyIterator()

결과

#1(__init__) : self=<__main__.MyIterator object at 0x0000028D8F603210> / self.current_value = 0

print(next(my_iterator))

#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8F603210>
#4(__next__) : result=0 / self.current_value = 1
0

try:
    print(next(my_iterator))
    print(next(my_iterator))
    print(next(my_iterator))
    print(next(my_iterator))
    print(next(my_iterator))
except:
    print("이터레이터가 종료되었습니다.")

#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8F603210>
#4(__next__) : result=1 / self.current_value = 2
1
#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8F603210>
#4(__next__) : result=2 / self.current_value = 3
2
#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8F603210>
#4(__next__) : result=3 / self.current_value = 4
3
#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8F603210>
#4(__next__) : result=4 / self.current_value = 5
4
#3(__next__) : self=<__main__.MyIterator object at 0x0000028D8F603210>
#5(else) : StopIteration 예외 발생!!
이터레이터가 종료되었습니다.

 

※ 안에있는 모든 자원을 다 사용하고 나면 오류나게 설정했으므로  try-except해줌!!

 


< 이터레이터 실습2>

 

<예제> 문자열을 전달 받아서 문자 하나씩 추출하여 반환하는 이터레이터 생성하기

 

 - 문자열 "Hello"의 각 문자 하나씩 출력하는 프로그램을 작성(기본)

str = "Hello"
for i in str:
    print(i)

 

 - 이터레이터 클래스 생성해서 Hello 각 단어 출력하기

   클래스 이름 : StringIterator

   임의의 문자열을 받아서 처리한다. : str2 = "Hello"

   임의의 문자열은 외부에서 클래스 생성시 넣어준다.

class StringIterator:
   
    def __init__(self, p_word):

        self.word = p_word
        # next함수에서 1씩 증가시키면서 반복 조건에 사용할 변수
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        
        if self.index < len(self.word):
            result = self.word[self.index]
            self.index += 1
            return result
        else:
            raise StopIteration

<이터레이터 실행시키기>

1번 방법 반복하여 전체 실행하기
 - 클래스 생성하기

str2 = "Hello"
str_iterator = StringIterator(str2)

 

 - 반복 수행하여 전체 출력하기

for v in str_iterator:
    print(v)

결과

H

e

l

l

o

 

 

클래스 생성할 때 값을 넣고 싶으면 init에 매개변수 넣어주기!!!!

※ 받아온 문자열 구분

   - self.word (멤버변수) : 클래스 내 공간이 할당되어있음

   - p_word (지역변수, 매개변수) : 받아온 값 

※ 반복수행을 위한 iter 함수 정의 : 무조건 자기자신의 class자원을 반환

※ 한건 한건 처리를 위한 next함수 정의 : 조건문 넣어줘야함, 조건 만족하지 않으면 오류 발생하도록(StopIteration)

 


< 이터레이터 실습3>

 

<예제> 일반 프로그래밍 방식과 이터레이터 방식의 메모리 비교

 

 - 메모리 확인을 위한 라이브러리 설치 필요 ( profiler )
 - prompt에서 conda activate gj_env_01 하고서  pip install memory-profiler

 - (실행이 안돼서 base로 가서 설치)

 - conda deactivate 하고서  pip install memory-profiler

 

주피터노트북

 

 - from + [폴더명] import + [.py의 파일명]

### from + [폴더명] import + [.py의 파일명]
from memory_profiler import profile

 

 - 주피터노트북에서는 아래 로드 처리 해야함 ( %load_ext )

%load_ext memory_profiler

 

- 이터레이터 클래스 생성하기

class SimpleIterator:
    def __init__(self, limit):
        #반복 범위를 지정할 값(반복의 끝값)
        self.limit = limit
        #반복 시작값
        self.current = 0
        
    def __iter__(self):
        return self
        
    def __next__(self):
        if self.current < self.limit:
            self.current += 1
            return self.current
        else:
            raise StopIteration

 

<데코레이터 함수 정의하기>

- profile의 라이브러리는 데코레이터 자동으로 만들 수 있다. ( @profile )

### 이터레이터를 사용하지 않고 메모리 체크하기
@profile
def no_iterator(limit):
    data = [i for i in range(1, limit+1)]

### 이터레이터를 사용해서 메모리 체크하기
@profile
def yes_iterator(limit):
    data = SimpleIterator(limit)
    for item in data:
        ###반복만 처리하고 별도 출력은 안함
        pass

 

 - %memit : 외부자원을 사용할 때 사용(주피터노트북만 사용!)

limit = 1000000
try:
    ### 데코레이터 함수 호출하기
    %memit no_iterator(limit)
    ### 이터레이터 함수 호출하기
    %memit yes_iterator(limit)
except:
    pass

결과

ERROR: Could not find file C:\Users\user\AppData\Local\Temp\ipykernel_22464\3269425511.py
peak memory: 104.99 MiB, increment: 25.90 MiB
ERROR: Could not find file C:\Users\user\AppData\Local\Temp\ipykernel_22464\3269425511.py
peak memory: 80.27 MiB, increment: 0.02 MiB

 

 

no_iterator는 메모리 증가가 25.9MiB 

※ yes_iterator는 메모리 증가가 0 MiB 

   - 따라서, 이터레이터를 사용하면 메모리 효율이 좋다는 것을 알 수 있다.

주피터노트북에서만 쓰는 것 : %load_ext,  %memit 

 

파이썬

 

 - 04_iterator_memory_test.py 파일 만들기

04_iterator_memory_test.py 생성

 

- 04_iterator_memory_test.py 코드작성 (주피터노트북에서 작성)

from memory_profiler import profile

class SimpleIterator:
    def __init__(self, limit):
        #반복 범위를 지정할 값(반복의 끝값)
        self.limit = limit
        #반복 시작값
        self.current = 0
        
    def __iter__(self):
        return self
        
    def __next__(self):
        if self.current < self.limit:
            self.current += 1
            return self.current
        else:
            raise StopIteration
### 이터레이터를 사용하지 않고 메모리 체크하기
@profile
def no_iterator(limit):
    data = [i for i in range(1, limit+1)]

### 이터레이터를 사용해서 메모리 체크하기
@profile
def yes_iterator(limit):
    data = SimpleIterator(limit)
    for item in data:
        ###반복만 처리하고 별도 출력은 안함
        pass
if __name__ == "__main__":
    limit = 1000000
    
    print("no_iterator -------------------\n")
    no_iterator(limit)
    
    print("yes_iterator -------------------\n")
    yes_iterator(limit)

    pass

 

 - 가상환경 들어가기

    > conda activate gj_env_01

 

 - 파일있는 위치로 이동

    > cd C:\Users\user\gj_202311\02_파이썬_알고리즘

 

 - 프롬프트에서 실행시 명령어
    > python -m memory_profiler 04_iterator_memory_test.py

 

파이썬파일 실행

 

결과 

no_iterator는 메모리가 83.4MiB 쓰이고 있고

yes_iterator는 메모리가 45.3MiB 쓰인다.

따라서, 이터레이터를 사용하면 메모리 효율이 좋다는 것을 알 수 있다.

 

※ 파이썬파일 실행시키려면 if __name__ == "__main__": 사용!!

 


< 이터레이터 실습4>

 

<예제> 두 개의 숫자(시작값, 종료값) 값을 이용해서, 짝수값만 반환하는 이터레이터 만들기

 

- 이터레이터 클래스 : EvenNumberIterator

class EvenNumberIterator:
    def __init__(self, start, end):
        self.start_num = start
        self.end_num = end
        
    def __iter__(self):
        return self
        
    def __next__(self):
  
        for i in range(self.start_num, self.end_num, 1) :
            ### self.start_num가 짝수인지 체크
            if self.start_num % 2 == 0:
                # 반환할 변수에 저장
                result = self.start_num
                # self.start_num 값은 1증가
                self.start_num += 1
                # 반환하기 : 반환하면 for문은 종료됨
                return result
            ### 짝수가 아니면
            else:
                # 1증가만 시키고 반복을 계속 수행
                self.start_num += 1

        ### for문을 이용한 경우에는, 이터레이터 반복 종료 후 마지막에 아래 추가
        raise StopIteration
even_iter = EvenNumberIterator(1, 10)
for v in even_iter:
    print(v)

결과

2

4

6

8

 

 

start부터 end까지 무조건 반복 실행
i값은 사용 안함
사용하는 값은 self.start_num만 사용

※ 짝수가 아니여도 self.start_num값 증가시켜주기!!

※ for문을 이용한 경우에는, 이터레이터 반복 종료 후 마지막에 아래 raise StopIteration 추가

 

 


< 이터레이터 실습5>

 

<예제> 외부 함수를 이용해서 짝수값 추출하는 이터레이터 만들기

 

- 이터레이터 클래스 : EvenNumberIterator

class EvenNumberIterator:
    ### 클래스 생성자 정의
    def __init__(self, start, end, func):
        #시작값
        self.start = start
        #종료값
        self.end = end
        #외부 함수
        self.func = func

    ###반복을 위한 이터레이터 함수 정의
    def __iter__(self):
        return self
    
    ###반복 결과값을 처리할 함수 정의
    def __next__(self):
        #시작부터 종료까지 while문 반복
        while self.start <= self.end:
            #외부함수로 짝수(True) or 홀수(False) 체크
            if self.func(self.start):
                result = self.start
                self.start += 1
                return result
            else:
                self.start += 1

        #이터레이터 종료하기
        raise StopIteration

 

 - 짝수와 홀수를 판별하는 외부함수 정의하기

def is_even(num):
    #리턴값 맞으면 True반환, 틀리면 False반환
    return num % 2 == 0

 

 - 클래스 생성하기

even_iter = EvenNumberIterator(1, 10, is_even)

for v in even_iter:
    print(v)

 

결과

2

4

6

8

10

 

 

 

※ 외부함수가져올  func 매개변수 생성하기!

 


< 이터레이터 실습6>

 

<예제> 텍스트 파일의 내용을 한줄씩 반환하는 이터레이터 만들기

 

- 이터레이터 클래스 : FileLineIterator
- 파일이름은 외부에서 받아서 open시키기

- 텍스트 파일 생성 : 04_example.txt ("C:\Users\user\gj_202311\02_파이썬_알고리즘\04_example.txt")

 

04_example.txt

 

 

내가 쓴 코드

class FileLineIterator:
    def __init__(self, path):
            self.path = path
            self.f = open(self.path, 'r', encoding = "utf-8")
            
    def __iter__(self):
        return self
        
    def __next__(self):
        
        result = self.f.readline()
        if result != "":
            return result
        else:
            self.f.close()
            raise StopIteration
file_read = FileLineIterator('04_example.txt')
for v in file_read:
    print(v)

결과

오늘의 저녁은??

백미밥

순두부백탕

카레소스

치킨너겟/강정소스

배추김치

 


 

교수님이 쓴 코드

class FileLineIterator1:
    def __init__(self, file_path):
        self.file_path = file_path
        self.file = open(file_path, "r", encoding = "utf-8")
        
    def __iter__(self):
        return self
        
    def __next__(self):
        line = self.file.readline()
        if line:
            return line.strip()
        else:
            self.file.close()
            raise StopIteration
file_path = "./04_example.txt"
file_reading = FileLineIterator1(file_path)
for v in file_reading:
    print(v)

 

결과

오늘의 저녁은??
백미밥
순두부백탕
카레소스
치킨너겟/강정소스
배추김치

 

 

 

UnicodeDecodeError : 파일 open 시기에서 읽어오지 못한다는 것, 따라서 utf-8로 encoding해줘야한다.
파일 읽어오기

      - 방법1 : open() 
          r : 읽기모드(텍스트 모드)
          f = open('foo.txt', 'r')
          f.readline()
          f.close()

      - 방법2 : with open()
          with open('./foo.txt', 'r') as f:
            print(f.readline())

방법2 는 이터레이터에서 사용x

     why? '파일을 읽어들이고 다 읽어들이면 자동 종료시켜라'

               파일 읽고 바로 close되므로 여기서는 사용못함 ( close()를 못하니까 )

readline() : '파일 정보중에 한 줄 가지고 와'
        - 최초 이후부터는 다음 줄이 있는지 자동으로 체크 후 가지고 온다.
        - 다음 줄이 없으면 안가지고 온다.

strip() : 문자열에서 공백 제거

 


 

이터레이터랑 비슷한 자바 문법이 readLine()이랑 nextLine()인데 이터레이터를 공부했으니

자바의 readLine()이랑 nextLine()를 복습해보자!

 

readLine

  • readLine (BufferedReader 클래스) : BufferedReader 클래스의 메소드로, 주로 텍스트 파일이나 다른 입력 스트림에서 한 줄을 읽을 때 사용.
  • readLine 메소드는 예외처리( try - catch )를 해주어야 한다.

 

< readLine 기본 구조>

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Example {
    public static void main(String[] args) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        try {
            System.out.println("Enter a line of text:");
            String line = reader.readLine();
            System.out.println("You entered: " + line);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

결과

Enter a line of text: [사용자 입력]               // 여기에 사용자가 입력한 문자열을 입력

You entered: [사용자 입력]                         // 사용자 입력이 출력됨

 


 

nextLine

  • nextLine (Scanner 클래스) : Scanner 클래스의 메소드로, 주로 키보드 입력이나 다른 입력 소스에서 데이터를 읽을 때 사용
  • nextLine 메소드는 예외 처리를 하지 않아도 된다.

 

< nextLine 기본 구조>

import java.util.Scanner;

public class Example {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.println("Enter a line of text:");
        String line = scanner.nextLine();
        System.out.println("You entered: " + line);

        scanner.close();
    }
}

결과

Enter a line of text: [사용자 입력]               // 여기에 사용자가 입력한 문자열을 입력

You entered: [사용자 입력]                         // 사용자 입력이 출력됨

 

 

728x90