본문 바로가기

Python/중급

Python - 중급 06 (closure)

closure(클로저)란?

어떻게 설명 드리면 좋을지 계속 고민하다가 모든 걸 접고 사용 예시를 보여 드리는게 가장 좋은 방법이라고 결론을 내렸습니다.

예시

def outer_function():
    message = 'Hi'

    def inner_function():
        print(message)

    return inner_function


my_closure = outer_function()
my_closure()
Hi
  • outer_function은 내부에서 inner_function을 정의하고 반환합니다.
  • inner_function은 outer_function의 지역 변수 message를 참조합니다.
  • outer_function()을 호출하면 inner_function을 반환합니다. my_closure에는 inner_function이 매핑됩니다.
  • my_closure를 호출하면 inner_function이 실행됩니다. 이 때 inner_function은 outter_function의 지역 변수인 message 변수의 값을 기억하고 있습니다. print(message)를 통해 message 변수의 값을 출력합니다.

파라미터가 있는 경우

def outer_function(msg):
    message = msg

    def inner_function():
        print(message)

    return inner_function


my_closure = outer_function('Hi')
my_closure()
Hi
  • 첫번째 예시와 다른 점은 outer_function의 파라미터로 message를 전달 받습니다.
  • outer_function('Hi')를 호출 할 때 전달된 인수 값 'Hi'를 inner_function이 기억하고 있다가 호출 될 때 출력됩니다.

인수를 다르게 호출

def outer_function(msg):
    message = msg

    def inner_function():
        print(message)

    return inner_function


hi_closure = outer_function('Hi')
hello_closure = outer_function('Hello')

hi_closure()
hello_closure()
Hi
Hello
  • outer_function을 호출 할 때 인수로 'Hi'와 'Hello'로 다르게 호출합니다.
  • outer_function의 결과로 각각 hi_closure와 hello_closure에 inner_function이 매핑됩니다.
  • hi_closure()와 hello_closure에()를 동일하게 정의된 inner_function이 실행되지만 저장된 message 변수의 값이 다르기 때문에 각각 다른 값인 'Hi'와 'Hello'가 출력됩니다.

nonlocal 키워드

def outer_function(msg):
    message = msg

    def inner_function():
        nonlocal message
        message = message + 'Hello'
        print(message)

    return inner_function


hi_closure = outer_function('Hi')
hi_closure()
Hello
  • inner_function에서 message 변수의 값을 변경하여 다시 저장하고 싶은 경우 nonlocal 키워드를 사용합니다.
  • nonlocal 키워드를 사용하지 않으면 message 변수는 inner_function의 지역 변수로 사용 되기 때문에 UnboundLocalError: local variable 'message' referenced before assignment 에러가 발생합니다.

closure의 특징

  • 외부 함수가 종료된 후에도 외부 함수의 지역 변수의 상태를 유지하여 해당 변수들에 대한 접근과 조작이 가능합니다.(message 변수의 값을 기억하고 있다가 호출 될 때 출력)
  • 외부에서 직접 접근할 수 없는 변수를 포함하여 데이터를 은닉하고 캡슐화합니다.(message 변수에 직접 접근 할 수 없습니다.)
  • 필요한 데이터만을 유지하므로, 메모리 사용에 있어서 효율적입니다. 큰 데이터 구조를 전역으로 유지할 필요가 없습니다.(message 변수만 유지하므로 메모리 사용에 효율적입니다.)
  • 실행 시점에 동적으로 동작을 생성할 수 있습니다. 동일한 외부 함수에 대해 다양한 동작을 하는 여러 클로저를 생성할 수 있습니다.(outer_function을 호출 할 때 파라미터를 다르게 전달하여 호출)

class를 이용한 동일 기능 구현

class를 이용하여 동일한 기능을 구현 할 수 있습니다.

class OuterClass:
    def __init__(self, msg):
        self.message = msg

    def __call__(self):
        print(self.message)


hi_closure = OuterClass('Hi')
hello_closure = OuterClass('Hello')

hi_closure()
hello_closure()
Hi
Hello
  • class의 생성자(__init__)에서 message를 인스턴스 변수로 저장합니다.
  • __call__ 메서드를 정의하여 인스턴스를 함수처럼 호출 할 수 있도록 합니다.
  • hi_closure()와 hello_closure()를 호출하면 __call__ 메서드가 실행되어 message 변수의 값을 출력합니다.

위와 같이 class를 이용하여 기능을 구현 할 수 있습니다. 하지만 closure를 이용하면 더 간단하게 구현 할 수 있습니다.

__call__

  • 매직 메서드(magic method)라고도 합니다.
  • __call__ 메서드를 정의하면 인스턴스를 함수처럼 호출 할 수 있습니다.
  • 위의 예시에서 OuterClass의 인스턴스인 hi_closure와 hello_closure를 함수처럼 호출 하였습니다.

연습 문제

  • 지정된 값에서 시작 하여 호출 될 때마다 1씩 증가하는 숫자를 반환하는 closure를 만드세요.
def counter(start=0):
    count = start

    def increment():
        nonlocal count
        count += 1
        return count

    return increment


c1 = counter(start=5)
print(c1())
print(c1())

c2 = counter(start=10)
print(c2())
print(c2())
6
7
11
12

'Python > 중급' 카테고리의 다른 글

Python - 비동기 프로그램(asyncio)  (0) 2024.07.15
Python - 중급 07 (decorator)  (0) 2024.01.10
Python - 중급 05 (generator)  (0) 2024.01.10
Python - 중급 04 (iterator)  (0) 2024.01.10
Python - 중급 03 (lambda)  (0) 2024.01.10