관련글 ▼
[인프런 리프 2기] 08. 파이썬 중급 과정 3주차(1) 시퀀스-1,2
[인프런 리프 2기] 09. 파이썬 중급 과정 3주차(2) 시퀀스-3,4
[인프런 리프 2기] 10. 파이썬 중급 과정 3주차(3) 일급함수-1,2
지난 번 수업에서 클로저를 클래스로 구현해보면서 클로저는 특별한 attribute를 사용해서 만드는 것이 아니란 걸 알았는데 클로저 심화와 데코레이터를 배우면서 확실하게 알았다. 함수를 활용해서 만든 함수(?)라고 이해하면 되는 거였다. 물론, 내 입장에서는 그렇게 외워야 머리 속에 잘 들어오고 이해하기 쉽기 때문에 단순화 시킨 것이지만 말이다.
외부에서 호출된 함수의 변수값, 또는 상태(reference)를 복사(snapshot)하고 저장 후에 접근(access) 가능하도록 해주는 것. 간단히 말하면 값을 저장해서 새로운 값과 함께 누적해준다는 의미.
# 클래스 이용
class Averager():
def __init__(self):
self._series = [] # [] 리스트형에 값을 넣는게 closure라고 보면 되겠네.
def __call__(self, v): # call을 이용하면 class를 함수처럼 사용할 수 있다.
self._series.append(v)
print('inner >> {} / {}'.format(self._series, len(self._series)))
return sum(self._series) / len(self._series)
앞서 클래스를 이용해서 closure를 구현해보았다. 이번에는 함수만 이용해서 closure를 구현해본다.
def closure_ex1():
series = []
def averager(v):
series.append(v)
print('inner >> {} / {}'.format(series, len(series)))
return sum(series) / len(series)
return averager
여기서 averager라는 함수 밖에 있는 series라는 변수는 free variable, 즉 자유변수라고 불린다. 자유 변수는 closure scope에서 내가 적용하려는 함수 바깥에서 선언된 변수라고 할 수 있다.
만약 series라는 변수가 global scope에 존재한다면 값은 누적된다. 하지만 현재 local scope에 존재하기 때문에 series(빈 리스트)는 averager라는 함수를 호출하는 순간 소멸(더이상 빈리스트가 아니며 값이 바뀌니까)된다.
avg_closure1 = closure_ex1
print(avg_closure1)
avg_closure1라는 변수에 closure_ex1을 그냥 호출하면 함수가 return된다. 왜? 해당 closure 안에 averager를 return 하라고 마무리지었으니까.
avg_closure1 = closure_ex1()
print(avg_closure1(10))
print(avg_closure1(30))
print(avg_closure1(50))
함수를 실행할 때마다 새로운 값이 누적되도록 만든다.
print(dir(avg_closure1))
print()
print(dir(avg_closure1.__code__))
print()
print(avg_closure1.__code__.co_freevars) # free variable을 출력
print(avg_closure1.__closure__[0].cell_contents) # contents를 출력
def closure_ex2():
# free variable
cnt = 0
total = 0
def averager(v):
cnt += 1
total += v
return total / cnt
return averager
분명 closure의 구조를 갖는 함수이다.
avg_closure2 = closure_ex2()
print(avg_closure2(10))
avg_closure2를 출력하면 UnboundLocalError: local variable 'cnt' referenced before assignment 오류가 발생한다.
왜? 우선은 averager라는 함수 scope 안에서 cnt라는 변수에 무언가 할당되어야 하는데, cnt 에 아무것도 할당되지 않은 상태에서 cnt에 1을 더하고 total에 v 값을 더하는 것이기 때문에 실행하면 오류가 발생하게 된다.
위와 같은 closure를 수정하는 방법은 아래와 같다.
def closure_ex3():
# free variable
cnt = 0
total = 0
def averager(v):
nonlocal cnt, total
cnt += 1
total += v
return total / cnt
return averager
nonlocal을 이용함으로써 averager 바깥 영역에 있는 cnt, total을 참조한다.
변수가 local 바깥, 즉 global scope에 있을 경우 함수 안에 global을 사용하는 것과 유사하다.
다만, global variable (전역변수) 를 사용하는 것은 실수가 발생할 가능성이 높아지므로 별로 좋지 않다. nonlocal이 local scope안에서 global같은 역할을 하는데 함수는 호출하지 않는 이상 실행되지 않으니 closure을 이용해서 변수를 할당하고 값을 누적하는 게 낫다.
클로저의 한 형태를 가지고 있으며 쉽게 말해서 장식해주는 역할을 한다고 보면 된다.
장점
단점
예를 들어 @classmethod, @staticmethod 같이 골뱅이(@)로 시작하는 친구들이 바로 decorator다. 나중에 다 만들어놓고 사용하기는 편하지만 만드는 게 복잡하고 힘들다.
요약: @로 작성된 예약어 밑에 작성된 함수가 곧 decorator
사람들이 무언가를 클릭할때마다 통계를 누적해주는 프로그래밍은 주로 static으로 짠다. 데코레이터로 통계 기능을 만들어주면 편하게 알 수 있다.
import time
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
위와 같은 decorator를 작성해준 다음 아무 함수를 짜본다.
def time_func(seconds):
time.sleep(seconds)
def sum_func(*numbers):
return sum(numbers)
non_deco1 = perf_clock(time_func)
non_deco2 = perf_clock(sum_func)
none_deco1이라는 변수에 perf_clock이라는 함수를 할당 및 time_func를 호출한다.
-> 복잡하게 짜야한다.
print('-'*40, 'Called None Decorator')
print()
non_deco1(1.5)
print('-'*40, 'Called None Decorator')
print()
non_deco2(100,200,300,400,500)
위의 결과를 출력해보면 아래와 같다.
---------------------------------------- Called None Decorator
[1.50970s] time_func(1.5) -> None
---------------------------------------- Called None Decorator
[0.00000s] sum_func(100,200,300,400,500) -> 1500
@perf_clock
def time_func1(seconds):
time.sleep(seconds)
def sum_func1(*nums):
return sum(nums)
데코레이터의 outer function을 @으로 선언해주면 데코레이터를 사용하는 것이 된다.
time_func1(1.5)
sum_func1(100,200,300,400,500)
훨씬 간략하게 원하는 값을 도출할 수 있다.
[0.00001s] sum_func(100,200,300,400,500) -> 1500
[1.50141s] time_func1(1.5) -> None
< 강의 출처 - 인프런 우리를 위한 프로그래밍 파이썬 중급 (Inflearn Original) >
[인프런 리프 2기] 12. 파이썬 중급 과정 4주차(2) 병렬성 (0) | 2021.03.31 |
---|---|
[인프런 리프 2기] 11. 파이썬 중급 과정 4주차(1) 병행성 (0) | 2021.03.31 |
[인프런 리프 2기] 10. 파이썬 중급 과정 3주차(3) 일급함수-1,2 (0) | 2021.03.27 |
[인프런 리프 2기] 09. 파이썬 중급 과정 3주차(2) 시퀀스-3,4 (0) | 2021.03.27 |
[인프런 리프 2기] 08. 파이썬 중급 과정 3주차(1) 시퀀스-1,2 (0) | 2021.03.25 |
댓글 영역