** 이 기능에 대해 '데코레이터'라는 명칭을 선택한 것에 대해 불만이 많았다. 그중 GoF 책에서 사용하는 용어와 일치하지 않는다는 불만이 가장 컸다. 데코레이터라는 명칭은 구문 트리를 파싱하고 애너테이션하는 컴파일러 분야에서의 용법과 관련이 더 깊다. _ PEP 318 - '함수 및 메서드 데코레이터'
특징 | Python 데코레이터 | GoF 데코레이터 |
주체 | 함수 또는 메서드 | 객체 |
목적 | 함수의 동작 변경/확장 | 객체의 동작 변경/확장 |
사용 방법 | 함수 위에 @데코레이터 사용 | 객체를 감싸는 데코레이터 객체 생성 |
Python 데코레이터는 하나의 함수(또는 메서드)를 다른 함수로 감싸서 추가 기능을 제공하는 도구입니다. 데코레이터는 함수의 동작을 수정하거나 확장할 때 유용합니다. 기본적인 형태는 다음과 같습니다:
- 기본적인 데코레이터 구조:
- 데코레이터 함수는 다른 함수를 인자로 받아서 새로운 함수를 반환합니다.
- 사용법:
- 데코레이터를 적용하려는 함수 위에 @데코레이터_이름을 붙입니다.
- 응용 사례:
- 로그 기록: 함수 호출과 결과를 기록하여 디버깅과 모니터링에 활용.
- 실행 시간 측정: 함수의 성능을 분석하고 최적화.
- 인증 및 권한 부여: 함수 호출 전에 인증 및 권한 확인.
- 캐싱: 함수의 결과를 캐시하여 성능 향상.
결국 데코레이터는 편리 구문(syntatic sugar)일 뿐이며 일반적인 콜러블과 동일하게 작동하지만 메타 프로그래밍을 할 때 편리합니다.
#1
@decorate
def target():
print('running target()')
#2
def target():
print('running target()')
target = decorate(target)
#1 와 #2는 본질적으로 같습니다.
하지만 #2의 방식은 아래 같은 단점이 있습니다.
- 가독성 저하: 함수 정의와 데코레이터 적용이 분리되어 코드가 장황해짐.
- 유지보수성 저하: 함수를 추가하거나 변경할 때 데코레이터 적용 부분도 수정해야 함.
- 실수 가능성 증가: 데코레이터 적용을 잊어버릴 가능성이 있음.
- 코드 중복 증가: 동일한 패턴의 반복으로 코드가 지저분해짐.
- 코드 일관성 문제: 함수 정의와 데코레이터 적용 방식이 일관되지 않음.
예제) 1. 로그 기록 데코레이터
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Function {func.__name__} is called with arguments {args} and {kwargs}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} returned {result}")
return result
return wrapper
@log_decorator
def add(a, b):
return a + b
add(3, 5)
Function add is called with arguments (3, 5) and {}
Function add returned 8
예제) 2. 실행 시간 측정 데코레이터
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Function {func.__name__} took {end_time - start_time} seconds to complete")
return result
return wrapper
@timer_decorator
def long_running_function():
time.sleep(2)
print("Function complete")
long_running_function()
Function complete
Function long_running_function took 2.0021233558654785 seconds to complete
데코레이터의 캐시 기능을 사용한 예제입니다.
import time
# 시간 차이를 계산하는 데코레이터 정의
def time_since_start(func):
start_time = time.time() # 데코레이터가 정의될 때 시작 시간을 기록
def wrapper(*args, **kwargs):
current_time = time.time()
elapsed_time = current_time - start_time
print(f"Time since start: {elapsed_time:.2f} seconds")
return func(*args, **kwargs)
return wrapper
# 데코레이터를 사용하여 함수 정의
@time_since_start
def example_function():
print("Example function is called")
# 함수 호출
example_function()
time.sleep(2)
example_function()
time.sleep(3)
example_function()
출력 결과
Time since start: 0.00 seconds
Example function is called
Time since start: 2.00 seconds
Example function is called
Time since start: 5.00 seconds
Example function is called
임포트 타임에서 time_since_start 함수가 호출될 때 start_time 변수가 선언되고 이후에 데코레이터에서는 wrapper 함수가 호출되면서 처음 선언된 start_time과의 시간 차이가 리턴 값으로 얻어집니다.
실제 사용 예제입니다.
django에서는 @login_required 라는 데코레이터로 api 요청이 왔을 때 사용자 인증을 거치고,login 상태가 아니면 login 페이지로 리다이렉트 하는 데코레이터를 사용합니다.
login_required는 사전에 정의된 함수이며 코드는 아래와 같습니다.
# django/contrib/auth/decorators.py
from functools import wraps
from django.http import HttpResponseRedirect
from django.utils.decorators import available_attrs
from django.conf import settings
def user_passes_test(test_func, login_url=None, redirect_field_name='next'):
"""
사용자 정의 테스트 함수를 통과하면 접근을 허용하는 데코레이터를 반환합니다.
Args:
test_func: 사용자 테스트 함수. 사용자 객체를 받아서 Boolean을 반환해야 합니다.
login_url: 로그인 페이지 URL. 기본값은 settings.LOGIN_URL입니다.
redirect_field_name: 리다이렉트 필드 이름. 기본값은 'next'입니다.
Returns:
view_func를 감싸는 데코레이터 함수.
"""
if not login_url:
login_url = settings.LOGIN_URL
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
# 사용자 정의 테스트 함수로 사용자를 검사합니다.
if test_func(request.user):
return view_func(request, *args, **kwargs)
# 테스트를 통과하지 못하면 로그인 페이지로 리다이렉트합니다.
path = request.build_absolute_uri()
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(path, login_url, redirect_field_name)
return _wrapped_view
return decorator
def login_required(function=None, redirect_field_name='next', login_url=None):
"""
로그인된 사용자만 접근할 수 있도록 보호하는 데코레이터입니다.
Args:
function: 데코레이터가 적용될 함수. 생략할 수 있습니다.
redirect_field_name: 로그인 후 리다이렉트할 때 사용할 GET 파라미터 이름. 기본값은 'next'입니다.
login_url: 로그인 페이지 URL. 기본값은 settings.LOGIN_URL입니다.
Returns:
view_func를 감싸는 데코레이터 함수. function 인자가 주어지면 즉시 데코레이터를 반환합니다.
"""
actual_decorator = user_passes_test(
lambda u: u.is_authenticated, # 사용자가 로그인되었는지 테스트합니다.
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
django에서 아래 같이 코드를 작성하면 my_view 함수를 실행하기 전 로그인 여부를 판단하고, 그에 따라 함수를 호출할지, 로그인 페이지로 이동할지 선택합니다.
# views.py
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
@login_required
def my_view(request):
return HttpResponse("Hello, you are logged in!")
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('my_view/', views.my_view, name='my_view'),
]
데이터를 다룰때 사용하는 dataclass 데코레이터도 있습니다.
from dataclasses import dataclass, field
@dataclass
class Person:
name: str
age: int = 30
hobbies: list = field(default_factory=list)
# 사용 예
p1 = Person(name="Alice")
p2 = Person(name="Bob", age=25)
p3 = Person(name="Charlie", hobbies=["reading", "swimming"])
print(p1) # 출력: Person(name='Alice', age=30, hobbies=[])
print(p2) # 출력: Person(name='Bob', age=25, hobbies=[])
print(p3) # 출력: Person(name='Charlie', age=30, hobbies=['reading', 'swimming'])
dataclass는 클래스에 __init__(), __repr__(), __eq__() 메서드를 자동으로 추가합니다.
'Computer Science > python' 카테고리의 다른 글
추상클래스의 활용 (0) | 2024.06.12 |
---|---|
프로토콜과 'abc' 모듈 (0) | 2024.06.11 |
seaborn clustermap color label (0) | 2022.05.24 |
flask_sqlalchemy (0) | 2022.05.23 |
python 설치 (0) | 2022.04.06 |