youngfromnowhere

[Python] recursive generator 본문

Python

[Python] recursive generator

곽일땡 2024. 12. 3. 17:35

https://stackoverflow.com/questions/38254304/python-can-generators-be-recursive

 

Python: can generators be recursive?

My recursive generator doesn't work: def recursive_generator(list_): yield list_[0] recursive_generator(list_[1:]) for k in recursive_generator([6,3,9,1]): print(k) Expected output: 6...

stackoverflow.com

 

앞서서 yield문을 이용하여 구현되는 generator에 대해 알아보았다.

그런데 이 generator를 recursive하게 구현하고 싶을 수가 있다.

먼저 list의 원소를 순서대로 print하는 function을

recursive하게 구현해보자.

def recursive_printer(_list):
    if not _list: # end the function when _list is empty
        return
    print(_list[0])
    recursive_printer(_list[1:])

if __name__ == "__main__":
    recursive_printer([8,6,4,2])

실행 결과는 다음과 같다.

8
6
4
2

이 function을 base로 list의 원소를 순서대로 반환하는 generator를 다음과 같이

작성해볼 수 있다.

def recursive_gen_1(_list):
    yield _list[0]
    recursive_gen_1(_list[1:])

if __name__ == "__main__":
   for k in recursive_gen_1([8,6,4,2]):
        print(k)

그러나 실행결과는 예상과 달랐다.

8

recursive_gen_1([8,6,4,2])을 호출하면서 generator-iterator에 대해 for loop 안에서 next()가 호출될 것이다.

그럼 recursive_gen_1()의 body의 첫 라인에 따라 iterator는 [8,6,4,2][0]인 8을 반환하고 이 data는 k에 할당된다.

그리고 여기서 generator의 실행은 '일시정지'된다.

 

이후 for loop의 print(k)가 실행되고

다음 loop가 돌면서 next()가 호출된다.

 

그리고 이 때 yield문의 다음 line인 recursive_gen_1(_list[1:])

즉 recursive_gen_1([6,4,2])가 호출된다.

 

여기서 처음 우리의 구현 의도는, recursive_gen_1([6,4,2])가 호출되면서 그 내부의 yield문이 다시 실행되는 것이었다.

하지만 여기서 문제는, recursive_gen_1([6,4,2])를 호출되면서 다시 generator-iterator가 생성되기는 하지만,

그 이후 아무 일도 일어나지 않는다는 것이었다.

 

즉, iterator를 생성하기만 해서는 [6,4,2]의 첫번째 원소인 6이 반환되는 일은 일어나지 않는다.

그렇다면 이 문제를 어떻게 해결해야 할까?

일반적인 generator의 형태를 다시 살펴보자.

def factors_gen(n):
    for k in range(1,n+1):
        if n%k == 0:
            yield k


def factors_gen2(n):
    k = 1
    while k*k < n:
        if n%k == 0:
            yield k
            yield n//k
        k += 1
    if k*k == n:
        yield k

yield문이 for-loop이나 while-loop 등의 반복문 안에 쓰인 것을 볼 수 있다.

즉, generator-iterator가 우리가 예상한대로 작동하려면,

next()호출로 body가 실행되어 yield문을 만난뒤 일시정지한 후

다음 next()호출로 그 다음 줄부터 다시 body가 실행될 때 다시 yield문을 만날 수 있도록

구현되어 있어야 한다.

 

다시 우리의 recursive_gen_1()을 보면, 최초의 yield문이 실행 된 뒤,

다음 next()호출 때  recursive_gen_1(_list[1:])이 호출되지만 그 뿐이라는 것을 볼 수 있다.

recursive_gen_1(_list[1:])는 generator-iterator를 생성하지만 이 iterator는

어딘가에 할당되지도 for-loop에서 쓰이지도 못했으므로 그 내부의 yield문 (우리가 6을 반환할 것이라 예상한 line)은

실행되지 못하는 것이다.

 

그렇다면 우리의 recursive_gen_1이 우리가 예상한 대로 작동하도록 보완하려면 어떻게 해야할까?

recursive하게 다시 generator를 호출하는 것은 마찬가지이지만,

이것을 for-loop에 넣어 body에서 생성된 iterator에 대해 next()가 호출되도록 하고

이것을 yield로 반환하도록 해야 한다.

 

def recursive_gen_2(_list):
    if not _list:
        return # end the iteration when _list is empty
    yield _list[0]
    for other in recursive_gen_2(_list[1:]):
        yield other

if __name__ == "__main__":
    for j in recursive_gen_2([8,6,4,2]):
        print(j)

 

실행결과는

8
6
4
2

 

실행과정을 분석해보자.



[main for-loop : 0]
recursive_gen_2([8,6,4,2]) iterator 생성
next(recursive_gen_2([8,6,4,2])) 호출
body 실행

yield [8,6,4,2][0] > 8 반환후 일시정지
8은 j에 할당 된 후 print(j)에 의해 출력


[main for-loop : 1]
next(recursive_gen_2([8,6,4,2])) 호출
body 실행
sub for-loop 진입 (for other in recursive_gen_2([6,4,2]))

    [sub for-loop : 0]
    recursive_gen_2([6,4,2]) sub iterator 생성
    next(recursive_gen_2([6,4,2])) 호출
    sub iterator body 실행

    yield [6,4,2][0] > 6 반환 후 일시정지

6은 other에 할당
yield other > 6 반환 후 일시정지
6은 j에 할당 된 후 print(j)에 의해 출력

[main for-loop : 2]
next(recursive_gen_2([8,6,4,2])) 호출
body 실행
sub for-loop resume

    [sub for-loop : 1]
    next(recursive_gen_2([6,4,2])) 호출
    sub-iterator body 실행
    sub-sub for-loop 진입 (for other in recursive_gen_2([4,2]))

        [sub-sub for-loop : 0]
        recursive_gen_2([4,2]) sub-sub iterator 생성
        next(recursive_gen_2([4,2])) 호출
        sub-sub iterator body 실행

        yield [4,2][0] > 4 반환 후 일시정지

    4는 other에 할당
    yield other > 4 반환 후 일시정지


4는 other에 할당
yield other > 4 반환 후 일시정지
4은 j에 할당 된 후 print(j)에 의해 출력

[main for-loop : 3]
next(recursive_gen_2([8,6,4,2])) 호출
body 실행
sub for-loop resume
    
    [sub for-loop : 2]
    next(recursive_gen_2([6,4,2])) 호출
    sub-iterator body 실행
    sub-sub for-loop resume (for other in recursive_gen_2([4,2]))

        
        [sub-sub for-loop : 1]
        next(recursive_gen_2([4,2])) 호출
        sub-sub iterator body 실행
        sub-sub-sub for loop 진입 (for other in recursive_gen_2([2]))

            [sub-sub-sub for-loop : 0]
            recursive_gen_2([2]) sub-sub-sub itereator 생성
            next(recursive_gen_2([2])) 호출
            sub-sub-sub iterator body 실행
            
            yield [2][0] > 2 반환 후 일시정지

        2 는 other에 할당
        yield other > 2 반환 후 일시정지

    2 는 other에 할당
    yield other > 2 반환 후 일시정지

2는 other에 할당
yield other > 2 반환 후 일시정지
2는 j에 할당 된 후 print(j)에 의해 출력

[main for-loop : 4]
next(recursive_gen_2([8,6,4,2])) 호출

...

                [sub-sub-sub-sub for loop : 0]
                recursive_gen_2([]) sub-sub-sub-sub itereator 생성
                next(recursive_gen_2([]))
                return 문을 만나 종료

 

'Python' 카테고리의 다른 글

[Python] Generator and yield  (0) 2024.10.23
[Python] Iterator  (0) 2024.10.23
[Python] pyenv로 가상환경 구축하기.  (0) 2023.09.19
[Python] python의 special methods  (0) 2022.11.17
[Python] Decorator3. Property  (0) 2022.11.16