본문 바로가기
개발/오늘의 개발일지

[Python] 반복문과 Lambda: 값 캡처와 변수 참조의 차이 이해하기

by 꾀돌이 개발자 2024. 11. 12.
반응형

 

반복문 속에서 Lambda를 사용할 때, 발생할 수 있는 오류를 확인합니다.

 

 

 
 

목차

     

    개발 의도

    # lambda 함수를 저장하기 위한 배열
    funcs = []
    
    # 배열에 lambda 함수 저장
    for i in range(3):
        funcs.append(lambda: print(i))
    
    # 루프가 끝난 후 모든 lambda 함수 실행
    for func in funcs:
        func()
    
    # 출력값: 2 2 2

    - 위와 같이 반복문과 lambda 함수를 이용하여 리스트에 print 함수 3개를 저장하는 코드를 작성합니다.
    - funcs에 저장된 함수를 순서대로 실행하여, 최종 출력값은 0, 1, 2가 되어야 합니다.

     

    문제 상황

    - funcs에 저장된 함수를 순서대로 실행했을 때, 최종 출력값이 2, 2, 2가 되는 상황이 발생합니다.

     

    일단 lambda 함수가 뭐지?

    - lambda 함수는 익명 함수로, def 키워드를 사용하지 않고 한 줄로 간단히 함수를 정의합니다,


        def aaa():
            return print(1)
        위와 같이 def로 함수를 정의할 때에는 함수에 aaa라는 "이름을 붙이지만",


        lambda: print(1)
        위와 같이 lamda로 함수를 정의할 때에는 "함수 이름 없이" print(1) 만을 작성합니다.

     

    lambda 함수 왜 써?

    - 짧고 간단한 함수를 빠르게 정의하기 위해 사용합니다.
    - 재사용하지 않을 작은 함수를 익명으로 정의할 때 편리합니다.

     

    코드의 출력값이 왜 2, 2, 2일까?

    - 위 코드에서는 lambda: print(i)가 루프 안에서 정의됩니다.
        그러나 중요한 점은, lambda 내부의 i는 변수를 참조하는 방식으로 동작합니다.
        즉, i의 값을 캡처하는 것이 아니라 i를 가리키는 참조를 저장합니다.

     

    변수 참조? vs 값 캡처?

    - 변수 참조 (Variable Reference)

    # lambda 함수를 저장하기 위한 배열
    funcs = []
    
    # 배열에 lambda 함수 저장
    for i in range(3):
        funcs.append(lambda: print(i))
    
    # 루프가 끝난 후 모든 lambda 함수 실행
    for func in funcs:
        func()
    
    # 출력값: 2 2 2

        위 코드에서 lambda: print(i)는 변수 i의 메모리 위치를 참조합니다.
            이 변수는 루프가 끝난 후 마지막 값으로 업데이트되기 때문에, 

            모든 lambda 함수가 i의 최종 값을 출력하게 됩니다.

    - 값 캡처 (Value Capture)

    # lambda 함수를 저장하기 위한 배열
    funcs = []
    
    # 배열에 lambda 함수 저장
    for i in range(3):
        funcs.append(lambda x=i: print(x))
    
    # 루프가 끝난 후 모든 lambda 함수 실행
    for func in funcs:
        func()
    
    # 출력값: 0 1 2

        위 코드에서는 x=i를 사용해 i의 현재 값을 복사하고,

            lambda가 이 값을 고정된 형태로 저장합니다.
            이렇게 하면 루프가 진행될 때마다 고유한 값이 각 함수에 캡처되어 저장됩니다.
            따라서 함수를 호출할 때 각 lambda가 자신만의 값을 출력합니다.

     

    비유로 이해하기

    - 변수 참조 (Variable Reference)
        작가가 전시회에서 사진을 가리키는 상황을 생각해보세요.
            메모에는 “작가가 가리키는 사진을 봐”라고 적혀 있습니다.
            시간이 지나 작가가 다른 사진을 가리키면, 

            메모를 읽는 사람은 항상 작가가 가리키는 최신 사진을 보게 됩니다.
         lambda: print(i)는 "i가 가리키고 있는 현재 값을 나중에 참조해"라는 의미로 저장됩니다.
            그래서 나중에 이 함수를 호출하면 i의 최종 값이 출력되는 거죠.

    - 값 캡처 (Value Capture)
         이제 각 사진을 복사해서 메모에 붙이는 상황을 떠올려 보세요.
             메모에는 “이 사진을 봐”라고 적혀 있고, 시간이 지나도 메모 속 사진은 변하지 않습니다.
         lambda x=i: print(x)는 i의 현재 값을 캡처해 x에 저장하는 것입니다.
             각 lambda 함수가 생성될 때 i의 값을 별도로 저장하니, 

             함수가 나중에 호출되더라도 고유의 값을 유지할 수 있습니다.

     

    문제 원인 파악

    - 즉, 루프가 끝난 후 i는 2가 되기 때문에, 배열에 저장된 모든 lambda 함수는 동일한 i를 참조하게 됩니다.
    - 따라서 모든 함수가 실행될 때 i는 2로 평가되며, 결과적으로 2 2 2가 출력됩니다.

     

    두 번째 코드로 디버깅 완료

    - 여기서 중요한 차이는 lambda x=i: print(x)에서 x=i를 사용해 기본 인수를 설정한 점입니다.
    - 이렇게 하면 루프가 진행될 때마다 i의 값이 고정된 상태로 lambda 함수가 생성됩니다.
    - 따라서 각 함수는 자신의 고유한 x 값을 가지게 되고, 0, 1, 2가 출력됩니다.

     

    반응형