클로저(Closure)는 자신이 선언될 당시의 환경(context)을 기억하는 함수를 말합니다. 클로저는 함수 내부에서 정의된 함수가 외부 함수의 변수에 접근할 수 있는 기능을 제공합니다. 이러한 구조는 데이터 은닉(data hiding)이나 상태 유지(state retention)가 필요할 때 유용하며, 특히 고급 함수형 프로그래밍에서 자주 사용됩니다.
- 데이터 은닉: 외부에서 접근할 수 없는 데이터 캡슐화를 가능하게 합니다.
- 상태 유지: 외부 함수가 종료된 이후에도 변수 상태를 유지할 수 있습니다.
- 코드 간결화: 재사용성과 간결성을 높이는 패턴을 제공합니다.
변수의 범위
클로저를 이해하기 위해서는 먼저 변수의 범위에 대하여 알고 있어야 합니다. 파이썬에서 변수의 범위는 LEGB 규칙을 따릅니다:
- Local: 함수 내부에서 정의된 변수.
- Enclosing: 중첩 함수에서 외부(상위) 함수의 변수.
- Global: 함수 외부에서 정의된 전역 변수.
- Built-in: 파이썬에서 기본 제공하는 내장 변수.
클로저에서 중요한 범위는 Enclosing입니다. 중첩 함수는 상위 함수의 Local 변수를 기억하여, 상위 함수가 종료된 이후에도 해당 변수에 접근할 수 있습니다.
def outer_function():
message = "I am an enclosing variable" # Enclosing 변수
def inner_function():
print(message) # 외부 함수의 변수 참조
return inner_function
# inner_function을 반환받아 실행
closure = outer_function()
closure() # 출력: I am an enclosing variable
위 예제에서 outer_function 안의 message 변수는 Enclosing 변수입니다. inner_function은 message 변수를 참조하고 있고, outer_function은 inner_function을 반환하고 있습니다. 본래 함수안의 변수는 함수가 수행되고 종료된 후에는 사라지게 됩니다만, message 변수는 inner_function에 의하여 언제든지 참조될 수 있습니다.
클로저 사용방법
위 예제처럼 클로저는 외부 함수에서 선언된 변수를 내부 함수가 캡처(capture)합니다. 그래서 외부 함수가 실행을 마쳐도, 내부 함수는 캡처된 데이터를 계속 사용할 수 있습니다.
클로저는 서버프로그램의 동시성에서 필요한 개념으로 교착상태(Deadlock)을 회피하기 위해 사용됩니다. 다수의 쓰레드가 메모리를 공유하지 않고 메시지 전달로 특정 작업을 처리함으로써 교착상태에 빠지는 위험을 방지할 수 있습니다.
주로 상태를 저장하는 데 사용되며, 콜백 함수나 데코레이터를 구현할 때 활용됩니다.
def outer_function(message):
def inner_function():
print(message) # 외부 함수 변수 참조
return inner_function
closure = outer_function("Hello, Closure!")
closure() # 출력: Hello, Closure!
예제1 : 클래스로 구현
누적평균을 구하는 클래스를 먼저 구현해 봅니다.
class cal_average():
def __init__(self) -> None:
self._series = [] # 멤버변수
def __call__(self, number): #클래스를 함수처럼 호출하게 한다.
self._series.append(number)
return sum(self._series) / len(self._series)
cumul_avg = cal_average() #인스턴스를 생성
print(cumul_avg(10)) # 출력 10
print(cumul_avg(20)) # 출력 15 <- (10 + 15)/2
위 클래스는 _series 라는 리스트형 멤버 변수에 입력 값을 계속 저장하고 호출할때마다 누적 평균을 구해 반환합니다. 매직 함수인 __call__ 을 오버라이딩 하면 클래스를 함수처럼 호출할 수 있게됩니다.
위 클래스를 클로저 함수로 구현해 봅니다.
예제2: 클로저 함수로 구현
def cal_average():
series = [] # 자유변수 영역
def exec_avg(number):
series.append(number)
return sum(series) / len(series)
return exec_avg
cumul_avg = cal_average() # 함수호출.
print(cumul_avg(10)) # 출력 10
print(cumul_avg(20)) # 출력 15 <- (10 + 15)/2
위 예제에서 외부 함수인 cal_average는 호출이 완료 되었지만, 자유변수 영역의 series 변수는 내부 함수인 exec_avg에 의해 여전히 접속가능합니다.
잘못된 클로저의 예
클로저 사용 시, 변수의 변경 가능한 속성 때문에 의도치 않은 동작이 발생할 수 있습니다.
def make_multipliers():
multipliers = []
for i in range(5):
def multiplier(x):
return x * i # i는 루프의 마지막 값(4)만 참조
multipliers.append(multiplier)
return multipliers
functions = make_multipliers()
print([f(2) for f in functions]) # 출력: [8, 8, 8, 8, 8]
수정된 코드
i 값을 기본 매개변수로 넘겨 문제를 해결할 수 있습니다.
def make_multipliers():
multipliers = []
for i in range(5):
def multiplier(x, i=i):
return x * i # i를 매개변수로 캡처
multipliers.append(multiplier)
return multipliers
functions = make_multipliers()
print([f(2) for f in functions]) # 출력: [0, 2, 4, 6, 8]
이 글에서는 클로저의 정의와 중요성, 변수 범위, 클로저의 개념과 동작 방식, 예제, 그리고 잘못된 클로저 사용 사례를 살펴보았습니다. 클로저는 코드를 더 간결하고 강력하게 만들지만, 잘못 사용할 경우 오류를 일으킬 수 있으므로 신중하게 활용해야 합니다.