ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Python] 파이썬 클로저(Closure)를 이해한다는 것
    IT 2026. 1. 21. 14:41

    클로저(Closure)는 공부하다 보면 한 번쯤은 반드시 마주치게 되는 개념입니다. 하지만 많은 사람들에게 클로저는 "개념은 설명할 수 있는데, 막상 왜 필요한지는 잘 모르겠는" 상태로 남아 있곤 합니다. 데코레이터나 고차함수를 배우다 보면 자연스럽게 등장하지만, 그 자체로는 어딘가 애매하게 느껴지는 개념이기 때문입니다.

    이제 클로저가 뭔지 알아보겠습니다.

     

    함수가 끝났는데, 변수는 왜 살아 있을까?

    클로저를 처음 접할 때 가장 많이 드는 의문은 이것입니다.

    보통 우리는 함수가 끝나면 그 안에서 사용하던 지역 변수들도 함께 사라진다고 배워왔습니다. 실제로 대부분의 경우 그렇습니다.

    def outer():
    x = 10
    print(x)
    
    outer()

    위 함수에서 x 는 outer 함수가 끝나는 순간 더 이상 접근할 수 없습니다. 이 흐름은 매우 자연스럽습니다.

    그런데 파이썬에서는 아래와 같은 코드가 가능합니다.

    def outer():
        x = 10
        def inner():
            print(x)
        return inner
    
    
    func = outer()
    func()

     

    outer 함수는 이미 실행이 끝났는데도, inner 함수는 여전히 x 의 값을 기억하고 있습니다.

    이때 바로 클로저가 만들어집니다.

    즉, 중첩 함수인 inner가 외부 함수인 outer의 변수 x를 참조하기에 x변수와 inner의 환경을 저장하는 클로저가 동적으로 생성되었고, func() 가 실행될때는 해당 클로저는 참조하여 x의 값을 출력할 수 있는 것 입니다.

    이 클로저는 func 변수에 outer 함수가 할당될 때 생성됩니다.

     

    그렇다면 이 클로저는 대체 어디에 존재하는 걸까요?

    이는 func 변수를 dir 함수로 출력해보면 알 수 있습니다.

    print(dir(func))
    
    ['annotations__', '__call__', '__class__', '__closure__', ...]

     

    클로저란 무엇일까?

    좀 더 정확하게 말하자면, 클로저란 자신이 정의될 당시의 외부 환경을 기억하고 있는 함수를 의미합니다.

    여기서 말하는 외부 환경이란, 함수가 선언될 때 접근할 수 있었던 변수들의 집합입니다.

    중요한 점은, 이 변수들이 단순히 값으로 복사된 것이 아니라, 연결된 상태로 유지된다는 점입니다. 그래서 함수가 끝난 뒤에도 그 값을 계속 사용할 수 있습니다.

    이 특성 덕분에 클로저는 마치 "자기만의 상태를 가진 함수" 처럼 보이게 됩니다.

     

    클로저는 왜 필요했을까?

    클로저가 필요한 이유를 이해하려면, 한 가지 상황을 떠올려 보는 것이 좋습니다.

    "함수 내부의 상태를 외부에 노출하지 않으면서, 계속 유지하고 싶다"

    클래스를 사용하면 이런 문제를 해결할 수 있습니다. 하지만 항상 클래스를 쓰는 것이 최선이 아닙니다. 아주 단순한 상태 하나를 위해 클래스를 만드는 것은 오히려 과할 수 있습니다.

    이럴때 클로저는 굉장히 좋은 선택지가 됩니다.

    def counter():
        count = 0
        def increase():
            nonlocal count
            count += 1
            return count
        return increase
    
    c = counter()
    print(c())
    print(c())
    print(c())

    이 코드에서 count는 외부에서 직접 접근할 수 없지만, increase 함수가 호출될 때마다 값이 유지됩니다. 클래스 없이도 상태를 가진 동작을 표현할 수 있는 것입니다.

     

    nonlocal 키워드는 왜 필요할까?

    위 예제에서 눈에 띄는 키워드가 하나 있습니다. 바로 nonlocal 입니다. 이 키워드는 많은 사람들이 클로저를 어려워하는 지점 중 하나이기도 합니다.

    파이썬에서 함수 안에서 변수에 값을 할당하는 순간, 그 변수는 기본적으로 지역 변수로 취급됩니다. 따라서 외부 함수의 변수를 수정하려고 하면 에러가 발생합니다.

    nonlocal 은 파이썬에게 이렇게 말하는 역할을 합니다.

    " 이 변수는 새로운 지역 변수가 아니라, 바깥 함수에 있는 변수를 사용할겠다."

    즉, 클로저가 단순히 값을 읽는 것에서 그치지 않고, 상태를 변경하려 할 때 필요한 선언이라고 이해하면 됩니다.

     

    클로저는 언제 자연스럽게 등장할까?

    클로저는 의도적으로 만들지 않아도, 파이썬 코드를 작성하다 보면 자연스럽게 등장하는 경우가 많습니다. 특히 고차원 함수와 사용할 때 그렇습니다.

    def multiply_by(n):
        def multiplier(x):
            return x * n
    return multiplier
    
    
    
    
    double = multiply_by(2)
    triple = multiply_by(3)
    
    
    print(double(5))
    print(triple(5))

    이 코드에서 double과 triple은 각각 다른 n을 기억하는 클로저입니다. 같은 함수 구조를 가지지만 서로 다른 환경을 품고 있는 셈입니다.

    이 패턴은 설정 값에 따라 동작이 달라지는 함수를 만들 때 매우 자주 사용됩니다.

     

    데코레이터와 클로저의 관계

    데코레이터를 이해했다면, 사실 이미 클로저를 사용하고 있었다고 볼 수 있습니다. 데코레이터의 내부 구조를 다시 떠올렵면, 대부분 이런 형태를 가지고 있습니다.

    def decorator(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
    return wrapper

    여기서 wrapper 함수는 func를 기억하고 있고, 이 관계 자체가 바로 클로저입니다. 즉, 데코레이터는 클로저를 활용한 대표적인 활용 사례라고 볼 수 있습니다.

     

    클로저를 이해하는 핵심 관점

    클로저를 이해할 때 가장 중요한 것은 문법이 아니라 관점입니다. 다음과 같은 질문에 대한 하나의 해답입니다.

    "상태를 가지는 함수를, 클래스 없이 표현할 수 없을까?"

    이 질문에 고개가 끄덕여진다면, 클로저는 더 이상 추상적인 개념이 아니라 매우 실용적인 도구로 보이기 시작할 것입니다.

     

    마무리하며

    클로저는 파이썬의 문법 중에서도 특히 오해를 많이 받는 개념 중 하나입니다. 하지만 한 겹씩 벗겨서 살펴보면, 결국 우리가 이미 알고 있던 함수의 성질을 자연스럽게 확장한 것에 불과하다는 사실을 알 수 있습니다.

    클로저를 제대로 이해하면 고차함수, 데코레이터, 함수형 프로그래밍 전반이 훨씬 명확해집니다. 그리고 파이썬 코드가 왜 이런 형태로 설계되었는지도 조금은 보이기 시작할 것 입니다.

    개념을 외우기 보다는, 이런 질문에서 출발해보세요.

    "이 함수는 무엇을 기억하고 있는가?"

    그 질문에 답할 수 있다면, 이미 클로저를 이해하고 있다고 볼 수 있습니다.

     

    읽어주셔서 감사합니다.

     

    다음 글에서 뵙겠습니다.

     

Designed by Tistory.