Python Closures
3 minute read

Closures (클로저)

이 글은 이곳의 내용을 정리, 보충한 것임을 먼저 밝힙니다.

중첩함수의 Nonlocal 변수

클로저에 대해 알기 위해서는 먼저 중첩함수nonlocal 변수의 개념을 알아야한다.

중첩함수란 간단히 말해 함수 내에 또 다른 함수가 정의된 것이다. 이때 이 둘을 각각 외부함수와 내부함수라고 한다. 내부함수는 자신을 감싸고 있는 외부함수의 스코프에 있는 변수들에 접근할 수 있는데, 여기서 발생하는 개념이 클로저(closure)이다.

어떤 함수 A를 가정해보자. 함수 A내에서 정의된 변수들은 이 함수의 local 변수들이다. 그리고 함수 A를 감싸고 있는 함수 B가 있다면, B의 변수는 A에게 non-local 변수가 된다. 즉 내부함수의 관점에서 보면 외부함수에서 정의된 변수들이 non-local 변수인 것이다. 원칙적으로 non-local 변수들은 내부함수에 의해 읽힐수는 있지만 변경될수는 없다. 내부함수가 non-local 변수를 수정하기 위해서는 nonlocal 키워드가 명시적으로 사용되어야 한다.


클로저 정의하기

Defining a Closure Function

중첩함수를 이용해 클로저 환경을 구성해보자.

# 클로저 환경이 구성되는 함수 구조 정의하기
def print_msg(msg): # 외부함수

  def printer(): # 내부함수
    print (msg) # 외부함수의 인자 msg를 읽어오고 있다.
  
  return printer

내부함수인 printer가 외부함수의 변수인 msg에 접근하고 있다.

# 함수 실행
print_msg("Hello")()
# 결과 : Hello

인자 msg"Hello"라는 string을 넘겨주고 함수를 실행시키면, Hello가 결과값으로 나온다. printermsg에 저장된 "Hello"를 이용하고 있는 것이다.


이번에는 한번 print_msg("Hello")another이라는 새로운 변수에 저장해보자.

another = print_msg("Hello")
another() 
# 결과 : Hello

"Hello"를 인자로 받은 print_msg 함수가 이번에는 another이라는 새로운 변수와 묶였다. 이 함수를 실행(another())하면, print_msg() 함수는 실행이 종료되었음에도 불구하고 "Hello"라는 메시지는 그대로 보존되어 있는 것을 볼 수 있다.

파이썬 이름공간의 원칙1에 의하면, 함수 내에서 선언된 변수는 함수의 실행이 끝나면 사라져야 한다. msg 변수도 이 원칙에 의하면 사라졌어야 하는데, 그 값이 계속 저장되어서 위의 코드가 문제없이 실행된다. (변수의 생성과 소멸에 대한 자세한 규칙은 이 곳을 참고)

이처럼 내부함수가 계속해서 외부함수의 변수를 기억하는 것클로저(Closure)라고 한다. 외부함수의 변수가 본래 자신의 스코프 밖에서도 기억되는 것이다. 심지어 외부함수가 삭제되어도 내부함수는 계속해서 이 값을 기억하면서 사용한다. 코드를 통해 확인해보자.

print_msg 함수를 이름공간에서 지워도 another()은 잘 실행되고, 이전에 msg에 저장되어있었던 “Hello”가 문제없이 출력된다.

del print_msg
another()
# 결과 : Hello

하지만 print_msg("Hello")를 실행하면 (당연하게도) 에러가 발생한다. print_msg 함수를 지워버렸기 때문에 글로벌 이름공간에서 해당하는 값을 찾지 못한 것이다.

print_msg("Hello")
# 에러 발생 (name 'print_msg' is not defined)

클로저의 생성 조건

When do we have a closure?

위에서 살펴봤듯이, 파이썬에서 클로저는 내부함수가 외부함수의 변수를 사용할 때 발생한다. 클로저가 발생하기 위한 조건은 다음의 세가지로 요약할 수 있다.


클로저의 용도

When to use closures?

어떤 문제들은 클로저를 이용하면 좀 더 객체지향적인 방법으로 풀 수 있다.

또 클래스를 정의할 때, 그 안에서 사용하려는 메소드의 수가 적다면 클로저를 이용하는 것이 나을 때도 있다. 하지만 만약 메소드의 수가 많으면 클래스를 이용하는 편이 낫다.

아래는 클래스보다 클로저를 이용하는 것이 나은 경우를 보여주는 예시이다. (하지만 사실 이 선호 또한 개인적인 취향에 따른다.)

def make_multiplier_of(n):
  def multiplier(x):
    return x*n
  return multiplier
  
times3 = make_multiplier_of(3)
times5 = make_multiplier_of(5)

print(times3(9)) # 결과 : 27
print(times5(3)) # 결과 : 15

Notes :memo:

  1. 파이썬의 여러 이름공간(namespace)들은 각자 다른 수명을 가지고 있다. (1) Built-in names : 파이썬의 내장함수들이 속해있는 이름공간이다. 인터프리터가 시작하면 만들어지고, 절대 지워지지 않는다. (2) Global namespace for a module : 인터프리터가 모듈의 정의를 읽을 때에 만들어지고, 대부분 인터프리터가 종료되지 않으면 지워지지 않는다. (인터프리터의 top-level invocation에서 행해지는 statement들은 __main__ 모듈에 속하고, __main__도 고유한 global namespace를 가지고 있다.) (3) 함수의 local namespace : 함수가 호출되면 만들어지고, 함수의 실행이 끝나면 사라진다. 

Recent Posts

Content Preserving Text Generation with Attribute Controls
Matching Networks for One Shot Learning
Pointer Networks
Get To The Point: Summarization with Pointer-Generator Networks
Learning Discourse-level Diversity for Neural Dialog Models using Conditional Variational Autoencoders