[Python] Closure 그리고 Decorator
in Python
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