[Python] 클로저(Closure)

Aiden·2022년 2월 21일
3
post-thumbnail

내부함수(Inner Function)


클로저에 대해 이해하기 위해서 내부 함수에 대해 간단히 살펴보고 넘어가자.

📌 함수 안에 정의되는 함수


내부 함수는 말 그대로 함수 안에서 정의되는 함수이다.

# greetings 함수 생성
def greetings(name):

	# inner 내부 함수 생성
	def inner():
    	print("Hello {}!".format(name))
    
    return inner

이전 일급객체 포스팅에서 다루었던 코드 예시인데,
name 을 파라미터로 받는 greetings 함수 내에서 인사말을 출력하는 inner 함수가 정의되고 있는 모습을 확인할 수 있다.
최종적으로 greetings 함수는 내부에서 정의된 inner 함수를 return 하고 있다.

func = greetings("Python")

따라서 greetings 를 호출해 변수에 할당하고 이를 출력하면,

print(func)
# Result
<function greetings.<locals>.inner at 0x000001E6B18C1040>

변수에는 greetings 의 return 값인 inner 함수 객체가 담기게 된다.

func()
# result
Hello Python!

그리고 변수를 메서드로 호출하게 되면, inner 함수가 호출되고 정상적으로 결과가 출력되고 있음을 확인할 수 있다.

하지만, 여기서 의문점이 생길 수 있다.
greetings 함수는 "Python" 파라미터와 함께 호출된 뒤 종료되었는데, func 는 어떻게 "Python" 파라미터를 기억하고 출력해낼 수 있었을까?

실제로 func 에는 inner 함수가 담겨있었으며, inner 함수는 어떠한 파라미터도 받지 않았는데 말이다.

이는 클로저로 인해 가능해진다.



클로저(Closure)


클로저의 정의는 다음과 같다.

📌 반환된 내부함수가 자신이 선언되었을 때의 환경을 기억하고, 외부함수 밖에서 호출되더라도 그 환경에 다시 접근할 수 있는 함수

즉, 자신이 선언되었을 때의 환경을 기억하고 있다가, 이후에 환경 밖에서 호출되었을 때도 다시 그 환경에 접근할 수 있는 함수가 바로 클로저이다.

위에서 이야기하는 환경 이란, 함수의 Scope 를 의미하는데, 이에 대해 알아보기 위해 함수의 Scope 에 대해 간단히 복습해보자.

def outer():
	x = 100
    
	def inner():
    	print(x)
    
    inner()
    
outer()
# Result
100

outer 함수 내에서 inner 함수를 호출하고 있는 내부 함수의 예시코드이다.

outer 함수 외부는 전역 Scope, 내부는 지역 Scope 가 되며, inner 함수는 outer 함수 지역 Scope 내의 중첩된(nested) 지역 Scope 를 생성한다.

Global Scope {
	outer()  # 1. Local Scope {
    	x
    	inner()  # 2. Local Scope {
        }
    }
}

이 때, inner 함수는 outer 함수의 내부에 선언되어 있기 때문에 inner 함수의 상위 Scope 는 outer 함수가 되고, 따라서 inner 함수는 x 에 접근할 수 있다. 하위 Scope 에서는 상위 Scope 에 접근할 수 있기 때문이다.

반대로, 상위 Scope 에서는 하위 Scope 로 접근할 수 없다.

  • Global ---> #1. Local Scope ❌
  • #1. Local Scope ---> #2. Local Scope ❌

추가적으로, 파이썬 인터프리터가 Scope 를 이동하며 변수에 접근하는 과정은 다음과 같다.

📌 Python Interpreter Variable Access

ⅰ)  inner 의 Scope 에서 x 를 탐색
ⅱ)  탐색 실패
ⅲ)  outer 의 Scope 에서 x 를 탐색
ⅳ)  탐색 성공

이번엔 클로저를 활용한 예시를 보자.
def outer():
	x = 100
    
    def inner():
    	print(x)
        
    return inner
    
func = outer()

func()
# Result
100

outer 함수가 inner 함수를 호출하지 않고 return 하는 예시 코드이다.

func = outer() 에서 outer 함수는 inner 함수를 return 하고 종료되었다. 따라서, outer 함수 Scope 내의 변수 x 는 제거되었고 더 이상 접근할 수 없어야 한다. 함수가 종료되면 해당 함수의 지역 Scope 도 함께 제거되기 때문이다.

하지만, 그 다음 func() 구문을 통해 outer 함수 Scope 내에 위치한 변수 x 가 정상적으로 참조되었고, 결과값도 올바르게 출력되었음을 확인할 수 있다.

즉, 클로저는 자신이 선언되었을 때의 환경, Scope 를 기억하고 있다가 외부 함수 밖에서 호출되었을 때 기억해놓은 Scope 에 다시 접근할 수 있음을 알 수 있다.

예시에서는 inner 함수가 outer 함수 내부에서 선언되면서 outer 함수의 지역변수 x 를 포함하여 Scope 를 기억하게 되고,
outer 함수가 종료되었다고 하더라도 이와 관계없이 기억해놓은 outer 함수의 Scope 에 다시 접근함으로서 지역 변수 x 를 참조할 수 있게 된 것이다.

정리

결론적으로 클로저는 내부함수가 반환되는 경우에 자신이 선언되었을 때의 환경인 Scope를 기억하고 있다가, 외부함수 밖에서 호출되면 해당 Scope 에 다시 접근할 수 있는 함수이다.

이 때, 클로저에 의해 참조되는 외부함수 지역 Scope 내의 지역 변수 x 를 자유변수라고 부른다.
클로저(Closure)라는 이름이 붙여지게 된 건, 이러한 자유변수에 함수가 닫혀있다(closed)는 뜻으로, 자유변수에 엮여있는 함수이기 때문이다.

클로저의 조건

앞서 살펴본 클로저의 예시 코드를 토대로, 클로저를 활용할 수 있는 조건들을 정리해보자면 다음과 같다.

📌 Conditions

  • 내부 함수
  • 외부 함수의 지역 변수를 참조
  • 외부 함수가 내부 함수를 Return

클로저는 내부 함수보다 외부 함수가 먼저 종료되었을 때, 만약 내부 함수가 외부 함수 Scope 내의 변수 혹은 함수 선언 등의 정보를 참조하고 있는 한 유효하다.
따라서, 클로저는 내부 함수여야 하며 외부 함수의 정보를 참조하여야 하는 것이다.

즉 위와 같은 조건들이 갖추어졌을 때 클로저를 활용할 수 있고, 클로저를 활용하게 되면 외부 함수가 종료되었어도 이를 참조하는 내부 함수가 존재하는 경우 계속 유지된다.

클로저의 활용

그렇다면 클로저를 사용하는 이유는 무엇일까?

우선, 클로저는 전역 변수의 사용을 억제할 수 있다.
특정한 상태를 기억하고자 하는 경우에 우리는 상태값을 전역 변수에 담아 유지하는 방법을 채택할 수 있는데, 이 때 의도하지 않은 실수로 인해 값이 변경될 우려가 있고 다른 변수와 충돌하는 일이 발생할 가능성이 있다.

하지만 상태값을 클로저를 활용하여 저장하게 된다면 비교적 안전하게 값을 유지하고 활용할 수 있게 될 것이다.

비슷한 맥락으로, Private 접근 제어자의 효과를 볼 수도 있다.
전역 변수를 활용하는 경우에는 의도하지 않은 엉뚱한 장소에서도 접근이 가능하기 때문에 예상하지 못한 부작용이 발생할 가능성이 있다.

하지만 클로저를 활용하게 되면, 변수를 사용하여야 하는 경우에만 접근할 수 있도록 외부로부터의 접근을 제한하는 효과를 간접적으로 얻을 수 있게 된다.

이는 함수가 메모리에서 사라진다고 하더라도 클로저를 통해 값을 유지할 수 있기 때문에 얻을 수 있는 이점이다.


이번 포스팅에서는 이렇게 내부 함수와 클로저에 대해 살펴보았고, 그 과정에서 Local Scope 와 global Scope 에 대한 복습도 할 수 있었다.

다음 포스팅부터는 이터레이터와 제너레이터, 파이썬의 고급 기능인 데코레이터까지 차근차근 알아볼 예정이다.



출처


2개의 댓글

comment-user-thumbnail
2022년 5월 27일

이해하기 쉽게 써주셔서 잘 읽을 수 있었습니다~!
감사합니다!

1개의 답글