개요
파이썬에는 데코레이터라(decorator)는 것이 있다.
직역하면 꾸미다 라는 뜻인데 말그대로 함수를 꾸미는 것이다.
만약 함수의 시간을 체크 하고 싶다고 해보자.
그럼 이렇게 할 것 이다.
import time
import random
def func():
time.sleep(random.random() * 1)
start = time.time()
func()
ellapsed_time = time.time() - start
print(f"Ellapsed Time: {ellapsed_time:.2f} seconds")
# Ellapsed Time: 0.55 seconds
시간을 재는 곳마다 저런 구조로 코드를 짜야한다면 매우 더러울 것이다.
시간을 재는 함수를 짜보자.
def func():
time.sleep(random.random() * 1)
def get_ellapsed_time(func):
start = time.time()
func()
ellapsed_time = time.time() - start
print(f"Ellapsed Time: {ellapsed_time:.2f} seconds")
get_ellapsed_time(func)
func 함수 자체를 get_ellapsed_time 에 넘겨서 위와 같이 짤 수 있다.
이정도도 사실 충분하지만 코드가 이쁘지는 않다.
시간을 재는 행위가 코드 구조에 영향을 미치는 것이 맘에 들지 않는다.
(우리는 불편함을 느껴야한다)
시간을 재면서 func 의 작업을 하는 함수를 만들어 보자.
def func():
time.sleep(random.random() * 1)
def get_time_func(func):
def time_func():
start = time.time()
func()
ellapsed_time = time.time() - start
print(f"Ellapsed Time: {ellapsed_time:.2f} seconds")
return time_func
time_func = get_time_func(func)
time_func()
get_time_func 으로 func 를 감싸면 func 의 작업도 수행하면서 시간을 재는 time_func 를 만들 수 있다.
이제 main 코드에서 시간을 재더라도 코드 구조가 바뀌지 않는다.
의식의 흐름처럼 왔지만
time_func = get_time_func(func)
해당 코드는
@get_time_func
def func():
time.sleep(random.random() * 1)
func()
@get_time_func 을 함수 정의 위에 붙여주는 것으로 같은 기능을 할 수 있다.
이것이 데코레이터 이다.
데코레이터에는 중요한 작업을 맡기지 않는다.
예시로 보여준 시간 체크 같이 주요 로직에 직접적 관련이 없는 작업에 주로 사용하는 것이 좋은 것 같다.
(@classmethod 의 경우 인자 값 자체가 바뀌긴한다.)
장점으로는 메인 소스코드의 로직이 전혀 바뀌지 않는다는 것이다.
타입 체크 (type check)
파이썬은 타입에 관대하지만 가면 갈수록 타입이 중요하다.
타입을 강제화 하는 데코레이터를 만들어보자
def add_int(a: int, b: int) -> int:
return a + b
이 함수는 오직 int 만을 위한 함수이다.
float 이 들어오기를 바라지 않는다.
그렇다면 인자의 type 이 int 가 아닌 경우 오류를 raise 해주는 장치가 필요하다.
def add_int(a: int, b: int) -> int:
if not isinstance(a, int):
raise TypeError("a 는 정수여야 합니다.")
if not isinstance(b, int):
raise TypeError("b 는 정수여야 합니다.")
return a + b
그런데.. int만 받아야하는 함수가 100개라면 어떨까.
타입체크를 위해 추가된 4줄(공백제외)의 소스코드가 400줄이 되어 버릴 것이다.
데코레이터를 사용하자.
def only_int(func):
def inner(a, b):
if not isinstance(a, int):
raise TypeError("a 는 정수여야 합니다.")
if not isinstance(b, int):
raise TypeError("b 는 정수여야 합니다.")
return func(a, b)
return inner
@only_int
def add_int(a: int, b: int) -> int:
return a + b
print(add_int(1, 2))
# 3
print(add_int(1, 2.))
"""
Traceback (most recent call last):
File ".\6.py", line 18, in <module>
print(add_int(1, 2.))
File ".\6.py", line 8, in inner
raise TypeError("b 는 정수여야 합니다.")
TypeError: b 는 정수여야 합니다.
"""
고급 타입 체크 (advanced type check)
float 만 받고 싶다면 어떻게 해야할까
only_float 데코레이터를 만들어서 사용하면 되겠지..?
str 만 받고 싶다면?
타입도 지정할 수 있으면 좋을 것 같다.
다음과 같이 하면 된다.
def type_check(t: type):
def inner1(func):
def inner2(*args):
for arg in args:
if not isinstance(arg, t):
raise TypeError(f"argument 는 {t} 타입 이여야 합니다.")
return func(*args)
return inner2
return inner1
@type_check(int)
def add_int(a: int, b: int) -> int:
return a + b
print(add_int(1, 2.))
"""
Traceback (most recent call last):
File ".\7.py", line 18, in <module>
print(add_int(1, 2.))
File ".\7.py", line 7, in inner2
raise TypeError(f"argument 는 {t} 타입 이여야 합니다.")
TypeError: argument 는 <class 'int'> 타입 이여야 합니다.
"""
'지식 > Python' 카테고리의 다른 글
[Database] Transaction (0) | 2023.08.24 |
---|---|
[python] Factory Pattern (0) | 2023.02.26 |
댓글