클로저(Closure) 란?
(Wiki 번역)
프로그래밍 언어에서의 클로저란 퍼스트클래스 함수를 지원하는 언어의 네임 바인딩 기술이다. 클로저는 어떤 함수를 함수 자신이 가지고 있는 환경과 함께 저장한 레코드이다. 또한 함수가 가진 프리변수(free variable)를 클로저가 만들어지는 당시의 값과 레퍼런스에 맵핑하여 주는 역할을 한다. 클로저는 일반 함수와는 다르게, 자신의 영역 밖에서 호출된 함수의 변수값과 레퍼런스를 복사하고 저장한 뒤, 이 캡처한 값들에 액세스할 수 있게 도와준다.
어렵다.. 직접 구현을 해보면서 보도록하자.
예제) 숫자를 여러번 입력 받아 누적하면서, 평균값을 구하는 기능을 구현해보자.
- 클로저를 사용하지 않는 경우,
# 결과를 누적하는 클래스 생성
class Averager():
def __init__(self):
# 결과 누적용 리스트
self._series = []
def __call__(self, v):
self._series.append(v)
print('class >>> {} / {}'.format(self._series, len(self._series)))
return sum(self._series) / len(self._series)
# 인스턴스 생성
avg_cls = Averager()
# 값을 누적되며 계산됨
print(avg_cls(15))
print(avg_cls(35))
print(avg_cls(40))
- 클로저를 사용하는 경우,
# 클로저 사용 (내부함수 사용, 클래스x)
def closure_avg1():
# Free variable : 내부 함수와 외부함수 사이의 영역
series = [] # 주의: 메모리에 계속 남아있음
# 클로저 영역,
def averager(v):
# Free variable 내의 series 변수에 값을 누적
series.append(v)
print('def >>> {} / {}'.format(series, len(series)))
return sum(series) / len(series)
# 함수이름만 반환
return averager # averager() 를 return 하면 실행되서 반환된다.
avg_closure1 = closure_avg1()
print(avg_closure1(15))
print(avg_closure1(35))
print(avg_closure1(40))
- Free variable 에 속하는 변수 확인
print(avg_closure1.__code__.co_freevars) # ('series',) 확인 가능
-> 위에서 보여지는 것과 같이,
별도 클래스 구현을 통한 인스턴스 생성 없이도, 함수 할당시(avg_closure1 = closure_avg1())에 Free variable 영역(series)을 계속적으로 참조하여 사용 가능하다는 강점이 있다.
처음봤을 때는 너무나 낯선 개념이라, 이해하기가 어려웠는데 사용하다보니깐 익숙해지긴 하더라..
python에서 클로저를 주로 사용하는 영역은 바로 decorator 영역이다.
decorator란 특정 함수를 wrapping 함으로써, 함수의 앞뒤에 추가적으로 실행되어질 구문 들을 정의해서 손쉽게 재사용 가능하게 해주는 것이다.
말은 조금 어렵지만 예시를 보면 쉽게 이해할 수 있다.
- 예시) 특정 함수를 실행할 때, 함수의 실행시간 및 함수명, 매개변수를 같이 print 하여, 출력해주자.
import time
# 데코레이터로 사용될 함수
def perf_clock(func):
def perf_clocked(*args):
# 시작 시간
st = time.perf_counter()
result = func(*args) # wrapping 된 함수 실행 (func 도 free variable 영역으로 저장됨)
# 종료 시간
et = time.perf_counter() - st
# 함수명
name = func.__name__
# 매개변수
arg_str = ','.join(repr(arg) for arg in args)
# 출력
print('Result: [%0.5fs] %s(%s) -> %r' % (et, name, arg_str, result))
# 실제 함수(wrapping)를 실행한 결과를 반환
return result
return perf_clocked
- Decorator를 사용하지 않는 경우,
def sum_func(*numbers):
return sum(numbers)
## decorator를 미사용하는 경우,
# 함수 할당
deco_exec = perf_clock(sum_func)
# 함수 직접 실행
deco_exec(100, 200, 300, 500)
# 결과
# Result: [0.00000s] sum_func(100,200,300,500) -> 1100
- Decorator를 사용하는 경우,
# wrapping 대상이 되는 함수
@perf_clock # wrapping 할 함수를 decorator 로 지정
def sum_func(*numbers):
return sum(numbers)
## decorator를 사용하는 경우,
# 함수 직접 실행
sum_func(100, 200, 300, 500)
# 결과 (동일)
# Result: [0.00000s] sum_func(100,200,300,500) -> 1100
-> 사실 이것도 처음보는 입장에서는 매우 낯설게 느껴지긴 한다.
그래도, 자주 쓰다보면 어느순간 익숙해지는 날이 오지 않을까..
'Python' 카테고리의 다른 글
제네레이터(Generator)란? (0) | 2020.03.26 |
---|---|
추상화 클래스 (abstract class) (0) | 2020.03.23 |
PEP8 이란? (0) | 2020.03.19 |
collections 의 모듈 namedtuple (네임드튜플) (0) | 2020.03.17 |
Instance(인스턴스) 메소드와 Class(클래스) 메소드 (0) | 2020.03.17 |