[Python] Closure 그리고 Decorator

1급 객체 (First Class Object)

  • 프로그래밍 언어에서 1급 객체란 다음 조건들을 만족하는 객체를 말함
    • 함수의 인자로 전달 가능
    • 함수의 반환값으로 전달 가능
    • 수정되거나 할당 가능
  • 파이썬에서는 list, str, int 같은 것들. 모두 함수 인자로 전달되고 반환이 될 수도 있고, 수정/할당이 됨
  • C언어에서는 함수의 인자로 함수를 전달할 수 없음. 따라서 C언어에서 함수는 1급 객체가 아님.
  • 파이썬에서는 함수가 1급 객체
def add(a, b):
	return a+b
def execute(func, *args):
	return func(*args)

f = add

execute(add, 3, 5) # 8

위 코드를 보면

  • f라는 함수는 execute 함수의 인자로 전달되었고
  • execute 함수에서 반환이 되었고
  • f 라는 변수에 할당되었음

따라서 파이썬에서 함수는 1급 객체임

변수 스코프

z = 3

def outer(x):
    y = 10
    def inner():
        x = 1000
        return x

    return inner()
  • inner 함수 블록 안에 있는 영역은 local 스코프
  • outer 안쪽 inner 바깥쪽에 있는 영역은 nonlocal 스코프. 여기서는 y 변수
  • outer 바깥쪽 영역은 global 스코프. 여기서는 z 변수

자신의 영역이 아닌 영역에 대해서 ‘읽기’는 가능하지만 ‘쓰기’는 제한됨

def count(x):
    def increment():
        x += 1
        print(x)
    increment()

count(5)
  • 위 코드는 increment 함수에서 nonlocal 영역에 있는 x 에 대해 쓰기 작업을 했기 때문에 UnboundLocalError 가 나게 됨
  • nonlocal 이라는 키워드를 붙이면 nonlocal 영역에 있는 영역에도 쓰기 작업 가능. global 도 마찬가지
  • 함수는 자신에게 가장 가까운 스코프부터 찾아나감. 위 코드에서 outer(10) 을 실행할 경우 local 스코프에 x=1000이 있기 때문에 outer 인자와 무관하게 1000이 나옴

Closure

  • 자신을 둘러싼 스코프의 상태값을 기억하는 함수
  • 어떤 함수가 클로져이기 위한 조건
    • 해당 함수는 중첩된 함수
    • 해당 함수는 자신을 둘러싼 함수 내의 상태값을 반드시 참조
    • 해당 함수를 둘러싼 함수는 이 함수를 반환
def in_cache(func):
    cache = {}
    def wrapper(n):
        print(cache)
        if n in cache:
            return cache[n]
        else:
            cache[n] = func(n)
            return cache[n]
    return wrapper

위에서 wrapper 함수는 클로저 조건을 모두 만족함

  • in_cache 함수 내에 중첩
  • in_cache 스코프의 cache 라는 상태값을 참조
  • 자신을 둘러싼 함수는 자신(wrapper)을 반환

아래처럼 팩토리얼 함수를 통해 클로저를 사용해보자

def factorial(n):
    ret = 1
    for i in range(1, n+1):
        ret *= i
    return ret

cache_fact = in_cache(factorial)

아래처럼 cache_fact 메소드를 호출할 때마다 nonlocal 변수인 cache 상태가 변하는 것을 볼 수 있음

cache_fact(3)
cache_fact(5)
cache_fact(10)

{}
6

{3: 6}
120

{3: 6, 5: 120}
3628800

함수가 실행됨에 따라 cache 상태가 업데이트 되고 있음

Closure 의 특징

클로저는 자신을 둘러싼 함수 스코프의 상태값을 참조하는데, 이 값은 함수가 메모리에서 사라져도 값이 유지됨

def times_multiply(n):
    def multiply(x):
        return n * x
    return multiply


times_3 = times_multiply(3)
times_4 = times_multiply(4)

times_3(5)
times_4(5)

15
20

times_multiply 를 메모리에서 삭제해도 생성된 클로저에는 영향을 미치지 않음

del(times_multiply)

times_3(5)

15

decorator 도 결국 클로저를 만드는 문법

위에서 in_cache를 통해 만든 클로저는 아래처럼 데코레이터로도 만들 수 있음

@in_cache
def factorial(n):
    ...

Closure 장점

  • 관리와 책임을 명확히 할 수 있고
  • 각 변수가 섞여 불필요한 충돌을 방지할 수 있으며
  • 사용환경(context)에 맞게 임의대로 내부구조를 조정할 수 있다.

팩토리얼 대신 N 까지 합을 더하는 함수를 클로저로 만들 수도 있음

@in_cache
def sum_to_N(N):
    s = 0
    for n in range(N+1):
        s += n
    return s

closure

참고


© 2021. All rights reserved.

Powered by Hydejack v9.1.4