python 클로저

이홍일·2020년 6월 26일
0

클로저의 정의

클로저의 사전적 정의에 대해 설명해 보겠습니다.

일급 객체 함수의 개념을 이용하여 스코프에 묶인 변수를 바인딩 하기 위한 일종의 기술이다. 함수가 가진 프리변수(free variable)를 클로저가 만들어지는 당시의 값과 레퍼런스에 맵핑하여 주는 역할을 한다. 클로저는 일반 함수와는 다르게, 자신의 영역 밖에서 호출된 함수의 변수값과 레퍼런스를 복사하고 저장한 뒤, 이 캡처한 값들에 액세스할 수 있게 도와준다.

쉽게 설명하면 어떤 함수의 내부함수는 클로저가 될 수 있으며, 클로저는 바깥 함수로부터 생성된 변수값을 변경 또는 저장 또는 엑세스할 수 있는 함수이다.
라고 합니다.

클로저를 설명하기 전 선행되어야 하는 개념들을 설명해 보겠습니다.

python에서 함수는 일급 객체이다

일급 객체란 1960년대 컴퓨터 과학자 크리스토퍼 스트래치가 제시한 개념입니다. 일급 객체는 다음과 같은 조건들을 만족해야 합니다.

  1. 변수나 데이터 구조 안에 담을 수 있다.
  2. 함수에 매개변수로 전달이 가능하다.
  3. 함수의 리턴값으로 사용될 수 있다.
def name_decorator(param):
    return param()

def greeting():
  return "Hello"

print(name_decorator(greeting))
# Hello

name_decorator 함수는 매개변수로 넘어온 greeting 함수를 호출하는 역할을 하고 greeting 함수는 "Hello" 이라는 문자열을 반환하는 역할을 합니다. name_decorator(greeting) 호출하면 greeting 의 반환값을 return해줍니다.

python의 함수는 위의 조건을 모두 만족하는 일급 객체입니다.

프리변수(free variable)

파이썬에서 프리변수는 코드블럭안에서 사용은 되었지만, 그 코드블럭안에서 정의되지 않은 변수를 뜻합니다.

중첩 함수(Nested Function)

중첩 함수, 혹은 내부 함수라고도 하는데 이는 함수 내부에 함수가 하나 더 있음을 말합니다.

def name_decorator(param):
    print('외부함수 영역')
    def inner():
        print('내부함수 영역')
        return param()
    return inner()

def greeting():
  return "Hello"

print(name_decorator(greeting))

# 외부함수 영역
# 내부함수 영역
# Hello

특징으로는 아래와 같이 내부함수를 외부에서 호출했을 경우
NameError: name 'inner' is not defined 에러가 발생합니다.

def name_decorator(param):
    print('외부함수 영역')
    def inner():
        print('내부함수 영역')
        return param()
    return inner()

def greeting():
  return "Hello"

print(inner())

이는 inner 함수는 name_decorator 안에 선언되었기 때문에 외부에서 접근을 못하기 때문입니다.

클로저

이제 클로저에 대해 설명해보겠습니다.
클로저의 조건으로는
1. 어떤 함수의 내부 함수여야한다.
2. 자신을 둘러싼 함수의 상태를 참조한다.
3. 외부함수는 해당 함수를 반환해야 한다.

def name_decorator(param):
    def inner(str):
        return param() + str
    return inner

def greeting():
  return "Hello"

closure = name_decorator(greeting)

print(closure(' 정우성'))

위의 조건을 만족하는 클로저를 생성해봤습니다.
1. inner 함수는 name_decorator 함수의 내부함수입니다.
2. 외부 함수인 name_decorator 의 파라미터 param 을 참조하며
3. 외부 함수인 name_decoratorinner 를 반환합니다.

외부 함수인 name_decorator 을 확인해보면 inner 함수를 객체 형태로 반환하고 있습니다. 이는 위에 언급했던 일급 객체를 다시 읽어보면 이해 되는 부분입니다.

closure = name_decorator(greeting)

이제 클로저를 사용해보겠습니다.
1. 먼저 외부함수인 name_decorator 함수에 파라미터를 greeting 함수를 객체 형태로 담은 다음 closure 에 할당했습니다. 그러므로 현재 closure 에는 name_decorator(greeting) 이 할당되어 있습니다.

print(closure(' 정우성'))
  1. 다음으로 name_decorator(greeting) 이 담긴 closure 에 (' 정우성') 이라는 값을 할당해서 print 해 보겠습니다.

Hello 정우성

이라는 값이 호출되는 것을 볼수있습니다.

closure = name_decorator(greeting) #inner

이는 위와같이 closurename_decorator(greeting) 를 할당했을때
name_decorator(greeting)inner 함수의 객체를 리턴했으므로
closure = inner 라고 볼수 있습니다.

closure(' 정우성') # inner(' 정우성')

그렇다면 이라고 볼수 있겠네요.

def name_decorator(param):
    def inner(str): # ' 정우성'
        return param() + str
    return inner

inner 함수의 파라미터로 ' 정우성' 을 받았습니다.
그렇다면 Hello는 어디서 들어오는걸까요?

closure = name_decorator(greeting)
  1. 처음 closurename_decorator(greeting) 을 할당할때
    greeting 함수는 'Hello' 를 리턴하기때문에 closure = name_decorator('Hello') 라고 볼수 있겠습니다.
  2. 이 시점에서 name_decorator 에 'Hello' 를 할당하면서 리턴되는 inner 함수 객체가 상위 함수인 name_decorator 의 파라미터인 param 에 할당되고 inner 함수에서 기억하게됩니다.

요약하자면 closure = name_decorator(greeting) 이렇게 closure 할당하는 순간

def name_decorator(param): # 'Hello'
    def inner(str): # ' 정우성'
        return param() + str
    return inner

name_decorator 의 파라미터인 param 에 'Hello' 가 할당된 상태로
inner 함수 객체를 리턴하기 때문에

print(closure(' 정우성')) # Hello 정우성

위 코드의 결과값이 Hello 정우성 으로 출력되는 것입니다.

클로저의 의미에서 명시하였던것 처럼

  1. 어떤 함수(name_decorator)의 내부함수인 inner 를 객체 형태로 리턴하여 클로저로 만들었고
  2. 클로저인 inner 의 바깥함수로부터 생성된 변수값(: 프리 변수, param) 을 변경 또는 저장 또는 엑세스 (return param() + str 을 통해 엑세스) 하는 클로저를 만들어 사용해보았습니다.

다음으로는 클로저의 응용인 decorator 에 대해 다뤄보겠습니다.

Decorator

decoratorclosure 처럼 중첩함수를 리턴하는 함수이며 다른 함수에 @function_name 형태로 적용시켜 적용된 함수가 실행되기 전에 무조건 실행되게 합니다. 즉 특정 함수를 실행하기 전에 강제적으로 다른 함수가 먼저 실행된후 실행되도록 하는 강제성을 제공하는 기능입니다.

def name_decorator(param):
  def wrapper(greeting):
    def decorator():
      result = greeting()
      return result + param
    return decorator
  return wrapper

@name_decorator("정우성")
def greeting():
  return "Hello "

print(greeting()) # Hello 정우성

위의 코드에서 greeting()을 호출하게되면 name_decorator 함수에 greeting 함수 를 파라미터로 넣게되는데 greeting 함수는 "Hello "를 리턴하기때문에 name_decorator("Hello ") 를 먼저 호출하게 됩니다.

그 다음 @name_decorator("정우성") 에서 "정우성" 을 파라미터로 name_decorator(greeting) 의 리턴값인 wrapper를 호출하게되는데 wrpper 의 파라미터로 "정우성" 을 넣기때문에 최종적으로 처음 넣은 "Hello " + "정우성" 이 호출됩니다.

profile
응애

0개의 댓글