해당 내용은 Datacamp의 Data engineering track을 정리했습니다.
Writing Functions in Python의 chapter 4에 대한 내용입니다.
기본적인 내용보다는 강의를 들으면서 처음 알게 된 내용을 위주로 작성했습니다.
해당 포스팅에는 아래의 내용을 포함하고 있습니다.
- 시간 측정하는 time 데코레이터 만들기
- decorator 설정된 function 정보 확인하기
- decorator에 인수 추가하기
- Timeout 데코레이터 만들기
1. Real-world examples
이번 강의에서는 time 라이브러리를 활용해서 런타임을 측정하는 데코레이터를 만듭니다. 보통 데코레이터는 이전 강의에서도 언급했듯이 다양한 함수에 동일한 함수를 적용해야 할 경우에 만듭니다.
import time
def timer(func):
"""A decorator that prints how long a function took to run."""
def wrapper(*args, **kwargs):
t_start = time.time()
result = func(*args, **kwargs)
t_total = time.time() - t_start
print(f'{func.__name__} took {t_total}s')
return result
return wrapper
@timer
def sleep_n_seconds(n):
time.sleep(n)
sleep_n_seconds(5) # sleep_n_seconds took 5.005095005035
2. Decorators and metadata
데코레이터를 사용한 function의 정보에 접근하려고 할 때, function의 정보가 나오지 않고, 데코레이터의 정보가 출력되는 문제가 있습니다.
@timer
def sleep_n_seconds(n=10):
"""Pause processing for n seconds.
Args:
n (int): The number of seconds to pause for.
"""
time.sleep(n)
print(sleep_n_seconds.__doc__) # 아무것도 안나옴
print(sleep_n_seconds.__name__) # wrapper
이런 경우에 function의 정보를 불러오고 싶을 때에는 functools의 wraps를 활용해야 합니다.
from functools import wraps
def timer(func):
"""A decorator that prints how long a function took to run."""
@wraps(func)
def wrapper(*argsm **kwargs):
t_start = time.time()
result = func(*args, **kwargs)
t_total = time.time() - t_start
print(f'{func.__name__} took {t_total}s')
return result
return wrapper
wraps 함수를 데코레이터로 사용하면, 원래 function의 정보로 접근이 가능합니다. 또한 function에 접근하고 싶으면 function.__wrapped__로 접근 가능합니다.
3. Decorators that take arguments
만약에 decorator에 인수를 추가하고 싶을 때는 어떻게 추가할 수 있을까요?
def run_n_times(n):
"""Define and return a decorator"""
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(n):
func(*args, **kwargs)
return wrapper
return decorator
run_three_times = run_n_times(3)
@run_three_times
def print_sum(a, b):
print(a + b)
print_sum(3, 5) # 8, 8, 8
@run_n_times(5)
def print_hello():
print('Hello!')
print_hello() # 'Hello!' 'Hello!' 'Hello!' 'Hello!' 'Hello!'
이전에 적용했던 것에다가 wrapper와 사용할 함수 사이에 decorator 함수를 추가해주면 됩니다. 그러면 위의 결과처럼 decorator에도 인수를 넘겨줄 수 있습니다.
4. Timeout(): a real world example
이번 강의에서는 함수가 생각보다 오래 실행되면 발생시키는 timeout을 데코레이터로 추가하려고 합니다.
import signal
def raise_timeout(*args, **kwargs):
raise TimeoutError()
# When an "alarm" signal goes off, call raise_timeout()
signal.signal(signalnum=signal.SIGALRM, handler=raise_timeout)
# Set off an alarm in 5 seconds
signal.alarm(5)
# Cancel the alarm
signal.alarm(0)
def timeout(n_seconds):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
signal.alarm(n_seconds)
try:
return func(*args, **kwargs)
finally:
signal.alarm(0)
return wrapper
return decorator
@timeout(5):
def foo():
time.sleep(10)
print('foo!')
@timeout(20)
def bar():
time.sleep(10)
print('bar!')
foo() #TimeoutError 에러발생
bar() #bar! 에러 발생X