[Python] 파이썬 데코레이터 이해하기 : 함수의 동작을 확장하는 도구
@perf_clock
def time_func(seconds):
time.sleep(seconds)
@perf_clock
def sum_func(*numbers):
return sum(numbers)
파이썬의 데코레이터는 함수나 메소드의 동작을 수정, 확장할 때 사용하는 도구로
데코레이터를 활용하면 코드의 중복을 제거하고, 공통 함수를 작성하여 유지보수성을 높일 수 있다.
특히 로깅, 프레임워크, 유효성 체크 등 공통 기능에 유용하게 적용할 수 있다는 점이 장점!
단점으로는 코드 가독성 감소, 디버깅이 불편한 점 등이 있다.
1. @ 데코레이터란 무엇인가?
데코레이터는 다른 함수를 감싸 그 함수의 동작을 변경하거나 확장하는 함수를 말한다.
기존의 함수에 영향을 주지 않으면서 추가적인 기능을 부여할 수 있다는 점이 특징
def simple_decorator(func):
def wrapper():
print("Function is about to be called.")
func()
print("Function has been called.")
return wrapper
@simple_decorator # 데코레이터 사용
def say_hello(): # say_hello 함수 정의
print("Hello!")
say_hello()
# 결과
# Function is about to be called.
# Hello!
# Function has been called.
위 코드와 같이 say_hello 함수를 호출하면
say_hello 함수를 인자로 받아 simple_decorator 함수를 반환한다.
2. 동작 원리
데코레이터는 함수를 인자로 받아, 또 다른 함수를 반환하는 방식으로 동작한다.
데코레이터를 적용할 때 @decorator_name을 함수 정의 바로 위에 붙여 사용한다.
def decorator_name(func):
def wrapper(*args, **kwargs):
# 원래 함수 호출 전 작업
result = func(*args, **kwargs)
# 원래 함수 호출 후 작업
return result
return wrapper
*args와 **kwargs를 사용해 인자 개수나 형태에 상관없이 모든 함수를 처리할 수 있다.
`*`를 붙이는 규칙
- *args:
- *args에서 *는 함수가 임의의 개수의 위치 인자를 받을 수 있도록 합니다. 이 인자들은 튜플로 처리된다.
- 예를 들어, def func(*args):라고 정의하면 func(1, 2, 3)처럼 여러 개의 인자를 전달할 수 있다. args는 (1, 2, 3)이라는 튜플로 함수 내에서 처리된다.
- **kwargs:
- **kwargs에서 **는 임의의 개수의 키워드 인자를 받아들인다. 이 인자들은 딕셔너리로 처리된다.
- 예를 들어, def func(**kwargs):라고 정의하면 func(a=1, b=2)처럼 여러 개의 키워드 인자를 전달할 수 있다. kwargs는 {'a': 1, 'b': 2}이라는 딕셔너리로 함수 내에서 처리된다.
- * 단독 사용:
- 함수 정의에서 *를 단독으로 사용하면, 그 이후의 인자는 키워드 전용 인자로 강제된다. 즉, 그 인자들은 반드시 key=value 형식으로만 전달될 수 있다.
- 예를 들어, def func(a, *, b, c):처럼 정의하면, func(1, b=2, c=3)처럼 b와 c는 반드시 키워드 인자로 전달해야 한다.
3. 예제
# 실행 시간 측정 : 함수의 실행 시간을 측정해보는 데코레이터
import time
def time_it(func):
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
print(f"{func.__name__} 실행 in {end_time - start_time:.4f} seconds")
return result
return wrapper
@time_it
def compute():
time.sleep(2)
compute()
# 결과
# compute 실행 in 2.0001 seconds
@time_it으로 데코레이터만 적용햇을 뿐인데 compute 함수를 실행하면 함수명과 실행 시간에 대한 데이터가 print문으로 출력된다.
# 권한 검사 : 사용자 권한에 따라 함수 실행을 제어하는 데코레이터
def requires_permission(permission):
def decorator(func):
def wrapper(user, *args, **kwargs):
if user.has_permission(permission):
return func(user, *args, **kwargs)
else:
raise PermissionError("Permission denied.")
return wrapper
return decorator
@requires_permission('admin')
def delete_user(user, user_id):
print(f"User {user_id} deleted by {user.name}")
# 가정: 현재 사용자가 admin 권한을 가지고 있는 경우
사용자가 유저 정보를 삭제할 경우 위와 같은 데코레이터를 활용하여 실행 여부를 판단할 수 있다.
# 중첩 데코레이터 사용
@decorator_one
@decorator_two
def my_function():
pass
중첩 데코레이터를 사용하는 경우 데코레이터는 아래에서 위 순으로 적용된다.
데코레이터가 없어도 프로그래밍을 하는데 문제는 없다. 하지만 데코레이터를 사용하면 훨씬 우아한 구조를 만들 수 있다.
# 심화 학습
- 데코레이터 패턴과 객체지향 프로그래밍의 융합
- 파이썬 내장 데코레이터 (@staticmethod, @classmethod, @property)의 동작 원리