ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Python - 제너레이터
    개발 2023. 2. 14. 16:49

    제너레이터

     

    파이썬에는 return 대신 yield 로 결과 값을 리턴하는 코드가 있는데 이 함수는 제너레이터를 생성하는 함수다. 제너레이터는 일반적으로 알고 있는 리스트랑 유사하다. 

     

    def multiple_list(number, limit):
        counter = 1
        value = number * counter
    
        ret = []
    
        while value <= limit:
            ret.append(value)
            counter += 1
            value = number * counter
    
        return ret
    
    def multiple_generator(number, limit):
        counter = 1
        value = number * counter
    
        while value <= limit:
            yield value
            counter += 1
            value = number * counter

     

    위의 코드에서 multiple_list와 multiple_generator는 모두 내부 로직에서 차이가 없다. 대신 multiple_list는 결과를 리스트로 줬고 아래 multiple_generator는 결과 값을 제너레이터로 준다. 

     

    ml = multiple_list(500, 5000)
    mg = multiple_generator(500, 5000)
    
    for ml_num, mg_num in zip(ml, mg):
        print(f"ml_num: {ml_num}, mg_num: {mg_num}")
    
    
    ml_num: 500, mg_num: 500
    ml_num: 1000, mg_num: 1000
    ml_num: 1500, mg_num: 1500
    ml_num: 2000, mg_num: 2000
    ml_num: 2500, mg_num: 2500
    ml_num: 3000, mg_num: 3000
    ml_num: 3500, mg_num: 3500
    ml_num: 4000, mg_num: 4000
    ml_num: 4500, mg_num: 4500
    ml_num: 5000, mg_num: 5000

     

    실행한 결과 출력값도 동일하다. 제너레이터나, 리스트나 결과 값이 동일하다면 굳이 따로 가져갈 이유는 없다. 그런데 제너레이터는 리스트와 결과물을 가져오는 방식이 다르다. 제너레이터는 데이터를 호출하는 시점에 코드를 실행하고 그 전까지는 함수 실행을 중지한다. for 문을 돌기 전까지는 어떤 코드를 돌릴 예정이라고만 알릴 뿐 실제로 어떤 값을 갖고 있는지는 모른다. 

     

    ml = multiple_list(500, 5000)
    mg = multiple_generator(500, 5000)
    
    print(f"ml: {ml}")
    print(f"mg: {mg}")
    
    #######################################
    
    ml: [500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000]
    mg: <generator object multiple_generator at 0x105142a40>

     

    print로 값을 출력해보면 리스트는 이미 결과 값을 갖고 있는 반면 제너레이터는 결과 값은 없고 주소값만 딸랑 있다. 아직 어떤 데이터가 있는지 모르기 때문이다. 

     

    import time
    
    def print_time(*args):
        print(time.strftime("%c", time.localtime(time.time())), *args)
    
    
    def multiple_generator(number, limit):
        counter = 1
        value = number * counter
    
        while value <= limit:
            yield value
            print_time(f"yield value {value}")
            counter += 1
            value = number * counter
    
    mg = multiple_generator(500, 5000)
    
    print(mg)
    
    for num in mg:
        print_time(f"print_num {num}")
        sleep(1)

     

    자세한 제너레이터 동작 방식을 알기 위해 중간에 sleep을 넣어봤다. 수정한 multiple_generator 함수에는 yield 바로 아래 print_time를 넣었다. 이 함수는 값을 호출하는 것 뿐만 아니라 호출하는 시점의 시간도 같이 출력한다. multiple_generator 상에서 값을 yield value를 호출하는 부분과 print_time 사이에 어떤 코드도 없기 때문에 로그상으로는 두 코드 호출 시점에 시간차가 없어야 한다. 그런데 실제 결과 값은 다음과 같다. 

     

    Tue Feb 14 16:24:17 2023 print_num 500
    Tue Feb 14 16:24:18 2023 yield value 500
    Tue Feb 14 16:24:18 2023 print_num 1000
    Tue Feb 14 16:24:19 2023 yield value 1000
    Tue Feb 14 16:24:19 2023 print_num 1500
    Tue Feb 14 16:24:20 2023 yield value 1500

     

    print_num 이 호출되는 시점과 yield value가 호출되는 시점 사이에 1초간 차이가 난다. 1초는 for 문 내에서 딜레이를 1초간 줬기 때문에 발생한 것이다. 실제 코드는 아래 순서대로 동작한다. 

     

    1. yield value

    2. for 문 내의 sleep(1)

    3. counter += 1

    4. value = number * counter

    5. yield value

     

    제너레이터에선 호출 시점에 데이터를 접근하고 그전까지는 함수의 실행을 중지한다. yield를 기점으로 왔다갔다 하는 방식이다. 리스트와 다르게 모든 데이터를 한번에 리턴하지 않아도 되기 때문에 메모리를 효율적으로 사용할 수 있다. 

     

    인공지능의 경우 대량의 데이터를 가져와야하는 경우가 있는데 yield를 적절하게 사용하면 메모리 공간을 아끼면서 처리할 수 있어서 유용할 것 같다. 왜 인공지능 라이브러리들은 대부분 파이썬인지 알 것 같다.

    '개발' 카테고리의 다른 글

    Flask SQLAlchemy vs SQLAlchemy  (0) 2023.02.14
    Python - namedtuple  (0) 2023.02.14
    Python - 데코레이터  (0) 2023.02.14
    Nodejs Blocking/Non-Blocking,  (0) 2023.01.26
    socketio redis adapter  (0) 2023.01.06

    댓글

Designed by Tistory.