아래 조건들을 만족하는 함수 X를 closure라고 한다.
closure의 가장 중요한 특성은 자신이 선언된 외부 함수의 scope(enclosed scope)의 변수 상태를 기억한다는 것이다. 이것은 closure의 중요한 특성이면서 closure를 사용하는 주된 이유가 된다.
def gugu(x):
def dan(y):
return x * y
return dan
dan_3 = gugu(3) # closure인 함수 dan은 외부 함수의 변수 x의 값을 기억한다
dan_7 = gugu(7)
print(dan_3(4)) # 3 * 4
print(dan_7(7)) # 7 * 7
del(gugu) # 외부 함수를 메모리 상에서 삭제
print(dan_3(6)) # 3 * 6
위 예제를 보면 closure는 자신의 외부 함수가 메모리에서 삭제되는 상황에서도 자신이 할당될때의 enclosed scope의 변수 상태를 기억한다. 자신의 바깥 scope의 상태를 어떻게 계속 기억할 수 있을까?
def gugu(x):
def dan(y):
return x * y
return dan
print(dan_3.__closure__[0].cell_contents) # 3
def all_add(a, b, c):
def add(d):
return a + b + c + d
return add
add123 = all_add(1, 2, 3)
print(add123.__closure__[0].cell_contents) # 1
print(add123.__closure__[1].cell_contents) # 2
print(add123.__closure__[2].cell_contents) # 3
파이썬에서는 함수가 closure일 경우 __closure__ 변수를 가지게 된다. __closure__는 튜플이며 각 원소의 cell_contents 값은 enclosed scope에서 참조한 변수의 값을 가지고 있다. 이 때문에 closure는 enclosed scope의 상태를 기억할 수 있다. (closure가 아닌 함수는 __closure__ 변수가 None값을 가진다.)
closure를 사용하면 어떤 장점이 있을까? 위에서 만든 구구단 함수를 closure 없이 만든다고 생각해보자.
1번의 경우는 중복되는 코드가 너무 많아지고, 2번의 경우에는 저런 함수가 구구단 하나라면 괜찮지만 전역변수를 써야하는 함수가 많아지면 관리하기가 힘들어진다. 즉, closure를 사용하면 중복을 피하면서 전역변수의 사용도 절제할 수 있다.
구구단 함수를 closure 없이 만드는 방법은 사실 하나 더 있다. class를 이용하는 것이다.
class Gugu():
def __init__(self, x):
self.x = x
def __call__(self, y):
return self.x * y
dan_3 = Gugu(3)
dan_7 = Gugu(7)
print(dan_3(5))
print(dan_3(9))
print(dan_7(3))
위와 같이 closure로 만든 구구단과 동일하게 동작한다. 또한 class도 독자적인 scope를 가지고 scope 내의 변수들을 기억한다. class, closure는 각각 객체와 함수를 생성하게 된다. 그럼 뭐가 더 좋을까?? 찾아보면 closure, class 둘다 각자의 장단점이 있고 상황에 맞게 사용하라고 한다.
class는 closure와 같은 용도로만 쓰이는 것이 아니고 __call__ 외에도 다양한 메서드들을 이용하여 객체를 더 폭넓고 유연하게 생성할 수 있다. 반면 closure는 class에 비해 좁은 용도에 쓰이지만 간단하고 가볍게 사용할 수 있다는 것이 장점이라고 한다.
객체보다는 함수가 필요하면서 어떤 state를 저장할 필요가 있을 때 closure를 사용하면 될 것같다. 또 closure는 decorator 같은 형태로도 편리하게 쓸 수도 있다.