데코레이터는 여러분이 작성하는 코드의 긍정적인 영향을 엄청나게 증폭시킬 수 있습니다.
파이썬 데코레이터는 사용하기가 아주 쉽습니다. 파이썬 함수를 작성하는 법만 알면 데코레이터 사용법을 배울 수 있습니다.
@somedecorator
def some_function():
print("Check it out, I'm using decorators!")
하지만 데코레이터를 작성하는 것은 완전히 다른 기술입니다. 그리고 그리 간단하지도 않으며, 다음과 같은 내용을 이해해야 합니다.
이 주제를 모두 이해하고 숙달하려면 상당한 시간이 걸립니다. 그리고 이것 말고도 이미 배워야 할 게 많을 겁니다. 시간을 내서 이런 주제들을 배울 만한 가치가 있을까요?
제 경우 이 질문의 답은 “아주 그렇다!”입니다. 그리고 분명 여러분도 그럴 것입니다. 데코레이터를 작성하는 것의 핵심 이점은 무엇이고, 무엇이 여러분의 일상적인 개발 업무를 손쉽고 강력하게 만들어줄 수 있을까요?
분석, 로깅, 인스트루먼테이션
특히 규모가 큰 애플리케이션에서는 현재 무슨 일이 벌어지고 있는지 구체적으로 측정하고 다양한 활동을 정량화하는 지표를 기록해야 할 때가 많습니다. 데코레이터는 그러한 중요 이벤트를 전용 함수나 메서드에 캡슐화함으로써 이러한 요구사항을 아주 가독성 높고 손쉽게 처리할 수 있습니다.
from myapp.log import logger
def log_order_event(func):
def wrapper(*args, **kwargs):
logger.info("Ordering: %s", func.__name__)
order = func(*args, **kwargs)
logger.debug("Order result: %s", order.result)
return order
return wrapper
@log_order_event
def order_pizza(*toppings):
# let's get some pizza!
카운트나 기타 지표를 기록할 때도 이와 동일한 접근법을 활용할 수 있습니다.
유효성 검사와 런타임 검사
파이썬의 타입 체계는 타입에 엄격하지만(strongly typed) 매우 동적입니다. 이 모든 이점에도 불구하고 파이썬의 이런 특성 때문에 자바 같은 정적 타입 언어라면 컴파일 시점에서 포착했을 법한 버그가 생길 수 있습니다. 하지만 시야를 좀 더 넓히면 들어오고 나가는 데이터에 대해 좀 더 세련된 맞춤형 검사를 강제하고 싶을 수도 있습니다. 데코레이터를 이용하면 이 모든 작업을 손쉽게 처리하고 한번에 여러 함수에 적용할 수 있습니다.
한번 상상해 봅시다. 함수가 여럿 있고, 각 함수는 딕셔너리를 하나 반환하는데, 이 딕셔너리에는 다른 필드와 함께 “summary”라는 필드가 포함돼 있습니다. 이 요약값은 80자를 넘으면 안 됩니다. 이를 위반하면 오류입니다. 다음은 이 같은 오류가 발생할 경우 ValueError를 던지는 데코레이터입니다.
def validate_summary(func):
def wrapper(*args, **kwargs):
data = func(*args, **kwargs)
if len(data["summary"]) > 80:
raise ValueError("Summary too long")
return data
return wrapper
@validate_summary
def fetch_customer_data():
# ...
@validate_summary
def query_orders(criteria):
# ...
@validate_summary
def create_invoice(params):
# ...
프레임워크 제작
데코레이터를 작성하는 법에 익숙해지고 나면 데코레이터를 사용하는 단순한 문법의 이점을 얻을 수 있습니다. 바로 사용하기 쉬운 언어에 시맨틱을 추가할 수 있다는 것입니다. 그다음으로 가장 좋은 것은 파이썬 자체의 문법을 확장할 수 있다는 것입니다.
사실 여러 인기 있는 오픈소스 프레임워크에서는 데코레이터를 사용합니다. 웹 애플리케이션 프레임워크인 플라스크(Flask)에서는 데코레이터를 사용해 HTTP 요청을 처리하는 함수로 URL을 라우팅합니다.
# For a RESTful todo-list API.
@app.route("/tasks/", methods=["GET"])
def get_all_tasks():
tasks = app.store.get_all_tasks()
return make_response(json.dumps(tasks), 200)
@app.route("/tasks/", methods=["POST"])
def create_task():
payload = request.get_json(force=True)
task_id = app.store.create_task(
summary = payload["summary"],
description = payload["description"],
)
task_info = {"id": task_id}
return make_response(json.dumps(task_info), 201)
@app.route("/tasks/<int:task_id>/")
def task_details(task_id):
task_info = app.store.task_details(task_id)
if task_info is None:
return make_response("", 404)
return json.dumps(task_info)
여기서는 app이라는 전역 객체와 이 객체의 route 메서드에서 특정 인자를 받는 것을 볼 수 있습니다. route 메서드는 핸들러 함수에 적용되는 데코레이터를 반환합니다. 내부적인 동작 원리는 상당히 난해하고 복잡하지만 플라스크를 사용하는 사람의 관점에서는 그 모든 복잡성이 완전히 감춰집니다.
이 같은 방식으로 데코레이터를 사용하는 것은 일반 파이썬 코드에서도 볼 수 있습니다. 예를 들어, 객체 시스템을 활용할 때 classmethod와 property 데코레이터가 사용됩니다.
class WeatherSimulation:
def __init__(self, **params):
self.params = params
@classmethod
def for_winter(cls, **other_params):
params = {'month': 'Jan', 'temp': '0'}
params.update(other_params)
return cls(**params)
@property
def progress(self):
return self.completed_iterations() / self.total_iterations()
이 클래스에는 세 개의 def 문이 있습니다. 하지만 각각의 시맨틱은 모두 다릅니다.
@classmethod와 @property 데코레이터의 단순함 덕분에 일상적으로 사용하는 파이썬 객체의 시맨틱을 확장하기가 쉬워졌습니다.
재사용 불가능한 코드의 재사용
파이썬에서는 표현력 있는 함수 문법, 함수형 프로그래밍 지원, 완전한 기능의 객체 시스템을 통해 코드를 손쉽게 재사용할 수 있는 형태로 캡슐화하는 강력한 도구를 제공합니다. 하지만 이것만으로는 처리할 수 없는 코드 재사용 패턴이 있습니다.
불안정한 API를 이용하고 있다고 해봅시다. HTTP를 통해 JSON 형식으로 요청을 보내면 99.9%의 경우에는 올바르게 동작합니다. 하지만 아주 일부 요청이 서버에서 내부 오류를 일으켜서 요청을 재시도해야 합니다. 이 경우 다음과 같은 재시도 로직을 구현할 것입니다.
resp = None
while True:
resp = make_api_call()
if resp.status_code == 500 and tries < MAX_TRIES:
tries += 1
continue
break
process_response(resp)
이제 make_api_call() 같은 함수가 여러 개 있고 이러한 함수를 코드 곳곳에서 호출한다고 상상해 봅시다. 함수를 호출하는 while 반복문을 그때그때마다 구현해야 할까요? 새로운 API 호출 함수를 추가할 때마다 같은 작업을 반복해야 할까요? 이 같은 패턴은 반복 작성 코드(boilerplate code)를 만들기가 어렵습니다. 데코레이터를 사용하지 않는다면 말이죠. 데코레이터를 사용하면 문제가 상당히 간단해집니다.
# 데코레이터가 적용된 함수에서는 Response 객체를 반환하고
# 이 객체에는 status_code 속성이 포함돼 있습니다.
# 200은 성공을 의미하고, 500은 서버 측 오류를 의미합니다.
def retry(func):
def retried_func(*args, **kwargs):
MAX_TRIES = 3
tries = 0
while True:
resp = func(*args, **kwargs)
if resp.status_code == 500 and tries < MAX_TRIES:
tries += 1
continue
break
return resp
return retried_func
이렇게 하면 사용하기 쉬운 @retry 데코레이터가 만들어집니다.
@retry
def make_api_call():
# ....
경력 업그레이드
처음에는 데코레이터를 작성하기가 쉽지 않을 것입니다. 하지만 로켓 과학도 아니라서 배우고 관련 내용을 세심하게 이해하려고 충분히 노력한다면 많은 개발자가 아무 문제 없이 데코레이터를 숙달할 수 있을 것입니다. 그리고 그것은 여러분에게도 유리하게 작용할 것입니다. 여러분이 팀 내에서 데코레이터를 작성하는 법을 배우고 실제 문제를 해결하는 데코레이터를 작성하게 되면 다른 개발자들도 데코레이터를 사용하게 될 것입니다. 데코레이터를 작성하는 힘든 일을 한번 하고 나면 데코레이터를 사용하는 것은 식은 죽 먹기입니다. 이는 여러분이 작성하는 코드의 긍정적인 영향을 대단히 증폭시킬 수 있습니다. 그리고 여러분을 영웅으로 만들어줄지도 모릅니다.
저는 지금까지 전 세계 곳곳을 여행하면서 수백 명의 소프트웨어 엔지니어가 파이썬을 더 효과적으로 활용하는 법을 가르쳐왔으며, 저의 고급 파이썬 프로그래밍 워크숍에서 배운 내용 가운데 데코레이터를 작성하는 것이 가장 귀중하고 중요한 도구라고 이야기해준 팀이 늘 있었습니다. 그리고 그런 이유로 다가오는 2016년 5월 25일과 26일에 있을 Python: Beyond the Basics 온라인 강좌에서도 데코레이터가 핵심적인 내용 중 하나입니다.
데코레이터를 작성하는 법을 어떻게 배우든, 데코레이터로 할 수 있는 일들과 여러분이 파이썬 코드를 작성하는 법을 데코레이터가 어떻게 영원히 바꿔줄지(농담이 아닙니다)에 관해서는 열광하셔도 좋습니다!
***
아론 맥스웰
아론 맥스웰은 『Advanced Python: A Not-For-Beginners Guide』의 저자이자 Advanced Python Newsletter의 편집자입니다. 십여 년에 걸쳐 다양한 실리콘밸리 스트타업의 인프라와 코드를 다양한 언어(주로 파이썬)를 이용해 구축하는 일을 하고 나서 지금은 전 세계 곳곳으로 여행을 다니면서 파이썬 개발자들에게 가장 강력한 비밀과 모범사례를 전파하는 일을 합니다. 또한 온라인 강의를 제공하기 시작해서 학생들이 아주 적은 금액으로도 배움의 혜택을 받게 하고 있습니다.
원문 : https://www.oreilly.com/ideas/5-reasons-you-need-to-learn-to-write-python-decorators
이전 글 : 웹 앱과 웹 사이트의 다른점 5가지
다음 글 : 파이썬이 인기 있는 교육적인 언어인 5가지 이유
최신 콘텐츠