클로저 심화
클로저 개념 제대로 알기
지난 글에서 다뤘던 클로저에 대해서 제대로 알아본다. 외부에서 호출된 함수의 변수값이나 상태를 복사 후 저장하는 것이 클로저의 개념이였다. 이렇게 값을 저장해놓고 나중에 접근할 수 있도록 한다.
함수 내부에 다른 함수를 두어 자유 변수(Free variable)을 기억하도록 만들 수 있다. 평균값을 구하는 averager를 클로저를 이용하여 작성해본다. 처음 closure_ex1을 실행했을 때는 함수 자체가 반환되는 것을 확인할 수 있다. 그리고 나서 변수값을 넣을 때마다, 클로저 영역에 있는 series에 값이 누적되면서 결과가 출력되는 것을 확인할 수 있다.
# Closure 사용
def closure_ex1():
# Free variable
# 클로저 영역
series = []
def averager(v):
series.append(v)
print("inner >>> {} / {}".format(series, len(series)))
return sum(series) / len(series)
return averager
avg_closure1 = closure_ex1()
print(avg_closure1) #<function closure_ex1.<locals>.averager at 0x1045601f0>
print(avg_closure1(10))
#inner >>> [10] / 1
#10.0
print(avg_closure1(30))
#inner >>> [10, 30] / 2
#20.0
클로저의 내부값들은 code 메서드를 통해 확인할 수 있다.
print(dir(avg_closure1.__code__))
print(avg_closure1.__code__.co_freevars)
print(avg_closure1.__closure__[0].cell_contents)
#['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_posonlyargcount', 'co_stacksize', 'co_varnames', 'replace']
# 위에서 'co.'로 시작하는 부분들이 클로저 관련 메서드들이다. 여기서 co_freevars를 조회해보면 자유 변수 값이 조회된다.
#('series',)
#[10, 30]
nonlocal
클로저를 사용할때는 반드시 변수값을 클래스 변수로 두어야한다. 메서드의 지역 변수로 두면 UnboundLocalError가 발생하면서 제대로된 클로저 기능을 사용할 수 없다. nonlocal 키워드를 통해서 지역 변수를 해제해주는 것이 중요하다.
def closure_ex2():
#Free variable
cnt = 0
total = 0
def averager(v):
nonlocal cnt, total
cnt += 1
total += v
return total / cnt
return averager
avg_closure2 = closure_ex2()
print(avg_closure2(10))
print(avg_closure2(30))
print(avg_closure2(60))
''' 출력 결과
10.0
20.0
33.333333333333336
'''
데코레이터
데코레이터는 자바의 어노테이션과 같이 부가적인 기능(로깅, 프레임워크, 유효성 체크)을 갖는 공통 코드를 함수화하고, 여러 곳에서 공통으로 사용할 수 있게 해준다. 다만, 로직이 캡슐화되므로 가독성이 감소하고 디버깅이 불편할 수 있다.
실습 예제
함수의 실행 시간을 측정하는 perf_clock 함수를 데코레이터로 만들어본다. 정확한 이해를 위해서는 앞서 배운 closure의 개념을 잘 알고 있어야 한다.
time 모듈의 perf_counter()는 실행 시점의 시각을 기록하며, sleep() 는 쓰레드의 실행 시간을 지연시킨다.
함수
def perf_clock(func):
def perf_clocked(*args):
# 시작 시각
st = time.perf_counter()
# 함수 실행
result = func(*args)
# 종료 시각
et = time.perf_counter() - st
# 실행 함수명
name = func.__name__
# 매개 변수 확인
arg_str = ', '.join(repr(arg) for arg in args)
# 결과 출력
print('[%0.5fs] %s %s -> %r' % (et, name, arg_str, result)
return result
return perf_clocked
함수 인자 선언, 결과 확인
위 perf_clock에서 인자로 받을 내부 함수를 선언하고, 각 내부 함수마다의 결과를 확인해본다. 데코레이터 없이 클로저 함수를 실행하기 위해서 외부 함수(perf_clock)에 들어갈 내부 함수(time_func, sum_func)를 정의하고, 할당하는 과정을 따로 거쳐야한다.
#인자로 들어갈 함수 선언
def time_func(seconds):
time.sleep(seconds)
def sum_func(*numbers):
return sum(numbers)
#함수 인자 할당
none_deco1 = perf_clock(time_func)
none_deco2 = perf_clock(sum_func)
#함수 및 자유 변수 확인
print(none_deco1, none_deco1.__code__.__co_vars)
print(none_deco2, none_deco2.__code__.__co_vars)
# 결과 확인
print('-' * 40, 'Called None Decorator -> time_func')
print()
print(none_deco1(1.5))
print('-' * 40, 'Called None Decorator -> sum_func')
print()
print(none_deco2(100, 200, 300, 400, 500))
# 결과
#<function perf_clock.<locals>.perf_clocked at 0x40021a4ca0> ('func',)
#<function perf_clock.<locals>.perf_clocked at 0x40021a4d30> ('func',)
#---------------------------------------- Called None Decorator -> time_func
#
#[1.50404s] time_func1.5 -> None
#None
#---------------------------------------- Called None Decorator -> sum_func
#
#[0.00058s] sum_func100, 200, 300, 400, 500 -> 1500
#1500
데코레이터 사용
데코레이터를 사용하면 외부 함수에 내부 함수를 할당하는 과정을 생략할 수 있다. 내부 함수를 정의하면서 그 위에 외부 함수를 데코레이터 형태로 매겨주면 된다. 이에 따라 함수 실행 시에도 내부 함수 자체를 호출하면 된다.
#인자로 들어갈 함수 선언(with decorator)
@perf_clock
def time_func(seconds):
time.sleep(seconds)
@perf_clock
def sum_func(*numbers):
return sum(numbers)
#데코레이터 호출 및 사용 확인
print('-' * 40, 'Called Decorator -> time_func')
print()
time_func(1.5)
print('-' * 40, 'Called Decorator -> sum_func')
print()
sum_func(100, 200, 300, 400 ,500)
#출력 결과
#---------------------------------------- Called Decorator -> time_func
#
#[1.50279s] time_func 1.5 -> None
#---------------------------------------- Called Decorator -> sum_func
#
#[0.00027s] sum_func 100, 200, 300, 400, 500 -> 1500
참조
1. 인프런 강의 - 우리를 위한 프로그래밍 : 파이썬 중급 (Inflearn Original)
'Programming-[Backend] > Python' 카테고리의 다른 글
파이썬 중급 - 8. 제너레이터 개념 되짚기, 코루틴 이해하기 (0) | 2022.07.24 |
---|---|
파이썬 중급 - 7. 병행성 흐름: iter, hasattr, isinstance, stopIteration, Yield, itertools (0) | 2022.07.23 |
파이썬 중급 - 5. 고위 함수(Higher Order Function), 클로저(Closure) 기본 (0) | 2022.07.17 |
파이썬 중급 - 4. 해시테이블(Dictionary), Set (0) | 2022.07.17 |
파이썬 중급 - 3. 시퀀스, 제너레이터, sorted, unpacking, immutable 등 (0) | 2022.07.17 |