[Python] Decorator
Published:
In this post, Python Decorator is introduced.
Decorator
Python에서 함수는 객체 이다. 그래서 함수를 다른 함수에 인자로 전달하거나 반환할 수 있다.
def hello():
print("hello")
def wrapper(func):
def new_func():
print("before")
func()
print("after")
return new_func
hello = wrapper(hello)
hello()
# 출력결과
before
hello
after
위 구조는 다음과 같다.
def 바깥 함수(인자 함수):
def 안쪽 함수():
...
인자 함수()
...
return 안쪽 함수()
변수 이름 = 바깥 함수(인자 함수) # decorator로 대체 가능!
변수 이름() # 안쪽 함수(인자 함수) 실행됨
이 때, 변수 이름 을 위 예처럼 인자 함수 이름과 동일하게 쓰는 경우가 흔하다. 이 경우, decorator를 쓰면 해당 줄을 대체할 수 있다.
@바깥함수
def 인자 함수():
...
위처럼 decorator를 이용한 코드는 아래 코드와 같다.
변수 이름(==인자 함수 이름) = 바깥 함수(인자 함수)
예시는 다음과 같다.
def log_decorator(func):
def wrapper():
print("function start")
func()
print("function end")
return wrapper
@log_decorator
def say_hi():
print("hi")
say_hi()
decorator를 사용하면 다음의 이점이 있다.
코드의 가독성이 좋아진다.
여러 decorator를 쉽게 쌓을 수 있다.
@auth @log @timer def api(): pass위와 같이 코드를 적는 것이 아래와 같은 일을 하지만, 가독성 측면에서 뛰어나다.
api = auth(log(timer(api)))
프레임워크 설계에 매우 중요하다.
Python 프레임워크 대부분이 decorator 기반이다. EX. FastAPI
@app.get("/users") def get_users(): pass이건 실제로
get_users = app.get("/users")(get_users)를 수행하는 코드이다. 위 코드의 수행 흐름을 단계별로 이해해보자.from fastapi import FastAPI app = FastAPI() @app.get("/users") def get_users(): return {"users": user_list} # user_list는 현재 서버 메모리나 데이터베이스에 저장된 사용자 목록FastAPI→ 클래스,app→ FastAPI 클래스의 객체(instance)app.get은 메서드이고,app.get("/users")은 메서드 호출. 이 함수 호출의 결과로 decorator 함수가 반환됨(step 1). 그리고get_users = decorator(get_users)가 실행됨(step 2). 내부 구조는 아래와 같음.class FastAPI: def get(self, path): def decorator(func): self.router.add_route( path, func, methods=["GET"] ) return func return decoratorrouter.add_route에서는 다음 정보를 Route 객체로 만들어 저장함. (step 2)Route( path="/users", endpoint=get_users, methods=["GET"] )그리고 이것을
router.routes에 추가함. (step 2)router.routes = [ Route("/users", GET, get_users), Route("/items", POST, create_item), ]클리이언트에서
/usersendpoint로 요청이 들어오면 HTTP request → ASGI server (uvicorn) → FastAPI app → router.match(path, method) → handler 함수 실행, 즉get_users()가 실행된다.decorator가 없었다면 다음과 같이 썼어야 했을 것이다.
def get_users(): pass app.router.add_route("/users", get_users, methods=["GET"])
메타 프로그래밍
- decorator는 함수 정의 순간에 코드 수정 이 가능하게 한다. 그래서 logging, authentication, caching, profiling같은 기능을 함수 내부 수정 없이 추가할 수 있다.
즉, Decorator는 기존 함수나 클래스의 코드를 바꾸지 않고 기능을 추가하는 Python 문법이다. 함수/클래스를 다른 함수로 감싸서 기능을 확장하는 것이다.
decorator 아래 정의되는 인자 함수 자리에는 함수뿐 아니라 클래스도 가능하다. dataclass decorator의 예시를 보자.
@dataclass
class Shot:
game_id: str
event_id: int
위 코드는 실제로 Shot = dataclass(Shot) 를 수행하며 dataclass() 함수가 Shot 클래스를 받아서 수정된 클래스를 반환한다. 참고로 dataclass 함수는 위처럼 field만 정의된 class를 인자로 주었을 때, 그 class의 type annotation 정보를 읽어 (Shot.__annotations__ 에 {'name': str, 'score': int}가 저장되어 있음) 자동으로 __init__ , __repr__, __eq__ 등을 만들어 주는 함수이다.
이 외에도 자주쓰이는 decorator로 @staticmethod, @property 등이 있다.

Leave a Comment