Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

파이썬 클로저(Closure) 완벽 가이드: 중첩 함수부터 __closure__ 속성까지

파이썬 클로저를 제대로 이해하려면 먼저 중첩 함수(nested function)파이썬 클래스에 대한 개념을 알아야 합니다. 간단히 말해, 파이썬 클로저란 코드와 데이터를 하나로 감싸는(encapsulate) 함수를 의미합니다.

파이썬 중첩 함수(Nested Function)

다른 함수 내부에서 정의된 함수를 중첩 함수라고 부릅니다. 중첩 함수는 자신을 둘러싼 바깥 스코프(enclosing scope)의 변수에 접근할 수 있다는 특징이 있습니다.

def funcOut():
    print("This is outer function.")
    def funcIn():
        print("This function is defined inside funcOut. \nThis function(funcIn) is called nested function.")
    print("We can call nested function here.")
    funcIn()

print("We are in outer function.\nCalling funcOut.")
funcOut()

실행 결과

We are in outer function.
Calling funcOut.
This is outer function.
We can call nested function here.
This function is defined inside funcOut.
This function(funcIn) is called nested function.

위 예제에서 funcInfuncOut 내부에 정의된 중첩 함수입니다. 실행 결과를 보면 함수들의 호출 순서를 쉽게 파악할 수 있습니다.

그렇다면 funcOut 외부에서도 funcIn의 기능을 그대로 사용하려면 어떻게 해야 할까요? 이때 위 프로그램에서 return funcIn처럼 중첩 함수를 반환하면 되는데, 바로 이것이 파이썬의 클로저입니다.

정리하자면, 클로저란 자신이 생성된 환경(바깥 스코프)을 기억하는 함수(객체)라고 할 수 있습니다.

클로저 기본 예제

def closureFunc(start):
    def incrementBy(inc):
        return start + inc
    return incrementBy

closure1 = closureFunc(9)
closure2 = closureFunc(90)

print('closure1(3) = %s' % (closure1(3)))
print('closure2(3) = %s' % (closure2(3)))

실행 결과

closure1(3) = 12
closure2(3) = 93

closure1(3)을 호출하면 12가 반환되고, closure2(3)을 호출하면 93이 반환됩니다. 흥미로운 점은 closure1closure2가 모두 동일한 함수 incrementBy를 참조하고 있음에도 불구하고, 서로 다른 결과를 낸다는 것입니다. 그 이유는 두 변수가 각각 closureFunc 호출 시점에 서로 다른 start 값(9와 90)과 결합되어 있기 때문입니다. 이것이 바로 클로저가 '생성 당시의 환경을 기억한다'는 의미입니다.

__closure__ 속성과 cell 객체

클로저의 내부 동작을 더 자세히 확인하고 싶다면 __closure__ 속성과 cell 객체를 활용할 수 있습니다.

def closureFunc(start):
    def incrementBy(inc):
        return start + inc
    return incrementBy

a = closureFunc(9)
b = closureFunc(90)

print('type(a)=%s' % (type(a)))
print('a.__closure__=%s' % (a.__closure__))
print('type(a.__closure__[0])=%s' % (type(a.__closure__[0])))
print('a.__closure__[0].cell_contents=%s' % (a.__closure__[0].cell_contents))

print('type(b)=%s' % (type(b)))
print('b.__closure__=%s' % (b.__closure__))
print('type(b.__closure__[0])=%s' % (type(b.__closure__[0])))
print('b.__closure__[0].cell_contents=%s' % (b.__closure__[0].cell_contents))

실행 결과

type(a) = <class 'function'>
a.__closure__ = <cell at 0x057F8490: int object at 0x68A65770>
type(a.__closure__[0]) = <class 'cell'>
a.__closure__[0].cell_contents = 9

type(b)=<class 'function'>
b.__closure__ = <cell at 0x0580BD50: int object at 0x68A65C80>
type(b.__closure__[0]) = <class 'cell'>
b.__closure__[0].cell_contents=90

위 출력 결과를 통해 각 클로저 객체의 cell이 함수가 생성된 시점의 값을 그대로 유지하고 있음을 확인할 수 있습니다. 즉, a의 클로저에는 start=9가, b의 클로저에는 start=90이 각각 저장되어 있는 것입니다.

핵심 정리

  • 중첩 함수는 바깥 함수의 스코프에 있는 변수에 접근할 수 있습니다.
  • 중첩 함수를 반환하면 클로저가 만들어지며, 클로저는 생성 시점의 환경을 기억합니다.
  • 동일한 함수를 반환하더라도 클로저마다 독립적인 상태를 가집니다.
  • __closure__ 속성으로 클로저가 캡처한 값을 확인할 수 있으며, 각 값은 cell 객체에 저장됩니다.