티스토리 뷰
Reference
- Fluent Python
- neillmorgan.wordpress.com
※참고로 클로저를 모른다면 데코레이터에 대해 이해하기 어렵다.
소개팅
def blind_date(name):
print(f"Hello My Name is {name}!")
필자는 안타깝게도 실제로 소개팅을 한 번도 해본 적은 없지만
만약 소개팅을 하게 된다면
먼저 자신의 이름을 상대방에게 말해줄 것이다.
근데 생각해보니
만나자마자 나의 이름을 말하는 것보단
악수를 먼저 하는게 좋다고 생각했다.
그래서 함수를 조금 수정해서
def blind_date(name):
print("handshake")
print(f"Hello My Name is {name}!")
악수를 먼저 하고 나의 이름을 말하는 구조로 코딩했다.
근데 생각해보니
소개팅할때 말고도 누군가를 만났을 때(부모님, 친구, 선생님...)는
최대한 친절하게!
무언가를 시작하기 전에 악수를 하고
끝났을때는 허그를 하는 동작을 하는 게 좋다고 생각했다.
def blind_date(name):
print("handshake")
print(f"Hello My Name is {name}!")
print("hug")
def when_i_met_my_teacher():
print("handshake")
print("many_func ...")
print("hug")
def when_i_met_my_parents():
print("handshake")
print("many_func ...")
print("hug")
def when_i_met_my_friends():
print("handshake")
print("many_func ... ")
print("hug")
그렇게 생각한 필자는
특정한 누군가를 만났을때 하는 각각의 동작의 집합이 담긴 함수에
첫과 끝에 악수와 허그를 추가했다.
그런데 생각해보니
친구들, 부모님, 선생님 외에도
학교 선후배, 직장상사, 친척들 등등 특정한 누군가가
너무나도 많다.
이 많은 사람들과 만난 상황에 대해
각각 악수와 허그를 추가하는 일을 해야 한다고 생각하니
벌써 앞날이 막막하다.
물론 우리에게 복사 붙여넣기가 있지만
만약 그렇게 한다고 하더라도
코드가 굉장히 지저분할거 같다.
이럴 땐 어떻게 해야 할까?
클로저를 통한 데코레이트
결국 필자가 해결하고 싶은 문제를 간단하게 요약하면
각각 다양하게 동작하는 메인 함수들이 있다면
그 메인함수들 시작과 끝에 동일한 동작을 추가하고 싶은 것이다.
물론 메인 함수 내부에 그 동작을 추가해도 동작은 하지만
클로저를 응용하면 조금 더 똑똑하게 문제를 해결할 수 있다.
def outer_func(func):
def inner_func():
print("handshake")
func() # main함수기준 위 아래
print("hug")
return inner_func
def when_i_met_my_friends():
print("many_func ... ")
def when_i_met_my_parents():
print("many_func ...")
meet_friends = outer_func(when_i_met_my_friends)
meet_parents = outer_func(when_i_met_my_parents)
meet_friends()
meet_parents()
outer_func은 클로저 형태 함수인데
혹시나 클로저의 대한 개념을 모른다면
부끄럽지만 이 글을 참고하기 바란다.
https://tcitr-antoliny.tistory.com/6
위 코드에 대해 간단하게 설명하면
outer_func을 호출하면 inner_func객체를 리턴하는
클로저 형태의 함수가 있고
inner_func은 outer_func의 매개변수를 자유 변수로 사용할 수 있다.
그 뜻은 inner_func에 있는
func()이 outer_func스코프에 있는 변수지만
inner_func이 자유변수로 사용할 수 있게 되기 때문에
우린 그 main함수 기준으로 위아래에 특정한 동작을 inner_func에 구현해주면 된다.
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
조금 더 현실적이고 복잡한 예시를 들기 위해
Fluent Python에 있는 코드를 보면
우리가 만약 함수들이 동작할 때마다
걸리는 시간을 계산하고 싶다면
import time
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args) # factorial
print(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print(f'[{elapsed:.8f}] {name}({arg_str}) -> {result}')
return result
return clocked
시간을 계산해주는 클로저 형태의 함수를 만들어준 뒤
clock의 매개변수에 함수들을 인수로 보내주면 된다.
위에서 설명했듯이
clock은 clocked함수 객체를 리턴하고
clocked함수는 자유 변수로 clock의 매개변수를 사용할 수 있다.
(★result = func(*args) 기준 위아래를 보면 main함수 위아래에 동작을 추가하는 개념이다.★)
import time
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args) # factorial함수객체
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print(f'[{elapsed:.8f}] {name}({arg_str}) -> {result}')
return result
return clocked
def factorial(n):
if n < 2:
return 1
else:
fac = clock(factorial)
return n * fac(n-1)
>>> [0.00000046] factorial(1) -> 1
>>> [0.00003154] factorial(2) -> 2
>>> [0.00003746] factorial(3) -> 6
>>> [0.00004208] factorial(4) -> 24
>>> [0.00004696] factorial(5) -> 120
>>> [0.00005238] factorial(6) -> 720
이렇게 클로저 형태로 우린 반복되는 코드를 각각의 다른 함수들에게
데코레이트 해서 해당 함수의 코드가 실행되기 전과 후에 특정한 동작을 할 수 있도록
만들어 주었다.
이건 어떻게 보면 데코레이터의 작동 방식이라고 할 수 있다.
이제 데코레이터를 사용해서
코드를 더욱더 간결하게 만들어 보겠다.
데코레이터
데코레이터 사용방법은 굉장히 간단하다.
일단 위와 같이 각각의 함수들에게 적용할 반복적인 동작을
클로저 형태의 함수로 만들어줘야 한다.
(★Decorate 말 그대로 '꾸미다'를 생각해보자★)
import time
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args) # factorial
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print(f'[{elapsed:.8f}] {name}({arg_str}) -> {result}')
return result
return clocked
이전에 우린 clock에 직접 함수 객체를 넣어 데코레이트 했지만
그럴 필요 없이
이런 특정한 동작을 추가하고 싶은 함수 위에
'@(outer_func)' --> 데코레이터를 써주면 된다.
# 데코레이터 사용 o
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
# 데코레이터 사용 x
def factorial(n):
if n < 2:
return 1
else:
fac = clock(factorial)
return n * fac(n-1)
데코레이터를 사용하지 않았을 때는
else문에서 factorial을 계속해서 호출해줘야 하기 때문에
그 호출하는 factorial객체도 데코레이트된 상태여야 한다.
그렇기 때문에 clock(factorial)클로저 함수로 계속해서
데코레이트를 시켜줘야 했지만
데코레이터를 적용하면
모든 factorial함수가 이미 clock클로저에 데코레이트 된 상태이기 때문에
clock(factorial) 코드가 필요 없다.
(이미 암묵적으로 이런 코드가 진행된 상태라고 생각하면 된다.)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
factorial(6)
>>> [0.00000054] factorial(1) -> 1
>>> [0.00003758] factorial(2) -> 2
>>> [0.00004379] factorial(3) -> 6
>>> [0.00004858] factorial(4) -> 24
>>> [0.00005358] factorial(5) -> 120
>>> [0.00005925] factorial(6) -> 720
이제 특정 함수들에게 반복되는 코드를 추가하고 싶다면
데코레이터를 무조건적으로 사용하는 게 좋을 거 같지만
데코레이터에게도 장단점이 있고
아래 글을 보면 적당한 선을 잘 찾아서
문제를 해결해야 하지 않나 싶다.
https://neillmorgan.wordpress.com/2010/02/07/decorator-pattern-pros-and-cons/
funtool.wraps나 Iru_cache, singledispatch 등등
표준 라이브러리에는 유용한 데코레이터들이 있다고 한다.
한 번 다양한 데코레이터를 찾아보는 것도 좋을 거 같다.
이번 기회를 통해 필자도 열심히 찾아봐야겠다.
👀
후기
필자가 마지막에
데코레이터에게도 장단점이 있고
적당한 선을 잘 찾아야 한다고 했는데
사실 인생에서 어떤 부분이든 객관적이고도 주관적인 적당함이 존재한다고 생각하고
그 적당함의 선을 잘 파악하는 게 어찌 보면
필자가 추구하는 인생철학의 한 부분이지 않나 싶다.
갑자기 왜 이런 포스팅과 전혀 관련 없는 말을 적는지 모르겠지만
나도 모르겠다.
가끔씩 나 자신도 나 자신을 이해하기 어렵다.
ㅠㅠㅠㅠ
'Python' 카테고리의 다른 글
[파이썬] yield from (0) | 2022.07.19 |
---|---|
[파이썬] 코루틴 (0) | 2022.07.11 |
[파이썬]인터페이스, 덕타이핑, 프로토콜 (1) | 2022.06.25 |
[파이썬]이터레이터, 이터러블, 제너레이터 (0) | 2022.06.24 |
[파이썬] 클로저(Closure) (0) | 2022.05.31 |