Let's Dive into Python Decorators (feat. Backend Examples)
Let's Dress Up Your Functions (Cute and Charming)

Let's Dress Up Your Functions (Cute and Charming)

Python has a feature called 데코레이터. You can use this feature in your code by writing @. If you use this feature, known as a decorator, effectively, you can achieve the following benefits.
It eliminates repetitive code, improves code readability, and enhances your understanding of the functionality.
This article introduces the basic concepts and usage of decorators, along with examples of their application in backend development, aimed at:
The goal is to help you understand and effectively use decorators.
A decorator is a higher-order function (HOF) that takes a function or class as an argument, processes the logic, and returns the function itself.
(Source: Wikipedia)
In mathematics and computer science, a higher-order function is a function that performs at least one of the following:
Although Python is not a functional programming language, it offers features comparable to those of functional programming. This is because, using the 'closure pattern', you can create functions that wrap other functions, as shown in the following example.
pythondef example_decorator(func): def wrapper(): print("변수로 받은 함수 **실행 전**에 수행하는 로직") func() # 변수로 받은 함수가 여기서 실행됨 print("변수로 받은 함수 **실행 후**에 수행하는 로직") return wrapper # 내부에서 선언한 wrapper라는 이름의 함수를 return
Higher-order functions allow you to pass other functions as parameters and execute them together.
pythondef my_function(): print("여기에서 무언가 로직이 실행됨") print("함수를 감싼 거니까") example_function = example_decorator(my_function) # 고차 함수를 실행하기 위해 함수 객체를 변수에 할당 example_function() # 할당된 변수로 함수를 호출
shell# 출력 값은 다음과 같다. "변수로 받은 함수 **실행 전**에 수행하는 로직" # 고차 함수에서 실행 "여기에서 무언가 로직이 실행됨" # 원하는 함수(my_function)에서 실행 "함수를 감싼 거니까" # 원하는 함수(my_function)에서 실행 "변수로 받은 함수 **실행 후**에 수행하는 로직" # 고차 함수에서 실행
Through this, you can see that the print statement in the higher-order function is executed before and after the function my_function is executed.
Decorators allow you to apply higher-order functions like the one above to other functions using the @ syntax. They also serve to explicitly indicate to the target function that "since a decorator is applied to this function, additional logic may be executed before and after the function,".
python@example_decorator # 이런 형식으로 쓰는 것을 '데코레이터'라고 함 def my_function(): print("여기에서 무언가 로직이 실행됨") print("함수를 감싼 거니까") my_function() # 대상 함수를 직접 호출하면서도 위와 동일한 print 결과를 얻을 수 있다.
You can apply multiple decorators to a single function. In this case, the 'order of decorators' is crucial, because changing the order of decorators alters the order in which the function is wrapped.
Suppose there are two decorators, as shown below.
pythondef example_decorator_1(func): def wrapper(): print("데코레이터 1에서 실행 (대상함수 실행 전)") func() # 변수로 받은 함수가 여기서 실행됨 print("데코레이터 1에서 실행 (대상함수 실행 후)") return wrapper def example_decorator_2(func): def wrapper(): print("데코레이터 2에서 실행 (대상함수 실행 전)") func() # 변수로 받은 함수가 여기서 실행됨 print("데코레이터 2에서 실행 (대상함수 실행 후)") return wrapper
1️⃣ When decorators are applied in the order Decorator 1 -> Decorator 2
python@example_decorator_1 @example_decorator_2 def my_function(): print("여기에서 무언가 로직이 실행됨") my_function()
shell# 출력 데코레이터 1에서 실행 (대상함수 실행 전) 데코레이터 2에서 실행 (대상함수 실행 전) 여기에서 무언가 로직이 실행됨 데코레이터 2에서 실행 (대상함수 실행 후) 데코레이터 1에서 실행 (대상함수 실행 후)

2️⃣ When decorators are applied in the order Decorator 2 -> Decorator 1
python@example_decorator_2 # 위와 달리 example_decorator_2를 먼저 실행 @example_decorator_1 def my_function(): print("여기에서 무언가 로직이 실행됨") my_function()
shell# 출력 데코레이터 2에서 실행 (대상함수 실행 전) # 대상 함수 실행 전 출력 순서도 달라짐 데코레이터 1에서 실행 (대상함수 실행 전) 여기에서 무언가 로직이 실행됨 데코레이터 1에서 실행 (대상함수 실행 후) 데코레이터 2에서 실행 (대상함수 실행 후) # 대상 함수 실행 후 출력 순서도 달라짐
💡 Therefore, there may be cases where the execution order becomes important. In particular, when using multiple decorators provided by various packages, dependencies may arise between the decorators, so this must be taken into account.
functools.wraps (Recommended Approach✅)Python functions have attributes called __name__ and __doc__, and when using decorators, their values may be altered.
pythondef example_decorator(func): def wrapper(): print(f"{func.__name__} 실행 전") func() print(f"{func.__name__} 실행 후") return wrapper @example_decorator def my_function(): print("여기서 무언가 로직이 실행됨") print(my_function.__name__) # 여기서 보통은 'my_function'이 출력 값이라고 예 상
shell# 출력 wrapper # 이는 example_decorator 내부에 선언된 wrapper의 이름으로 덮어씌워져 출력된 것
Consequently, you can prevent this by using Python’s built-in functools.wraps.
functools.wraps is also a higher-order function that can be used as a decorator, ensuring that the attributes of the target function remain unchanged. Therefore, this method is primarily used to define a wrapper function within a higher-order function.
pythonfrom functools import wraps def example_decorator(func): @wraps(func) # wraps도 데코레이터로써 사용됨 def wrapper(): print(f"{func.__name__} 실행 전") func() print(f"{func.__name__} 실행 후") return wrapper @example_decorator def my_function(): print("여기서 무언가 로직이 실행됨") print(my_function.__name__)
shell# 출력 my_function
*args and **kwargsNaturally, if the target function has arguments, you must receive and use them as-is. In this case, you can easily do so by using *args and **kwargs.
pythondef double(func): @wraps(func) def wrapper(*args, **kwargs): # func에 줄 인자를 받는다 args = list(args) args[0] *= 2 # 두 배 씩 늘리기 args[1] *= 2 # 두 배 씩 늘리기 return func(*args, **kwargs) return wrapper @double def add_digits(a, b): return a + b print(add_digits(2, 3))
shell# 결과 10
When using web frameworks such as Flask, Django, or FastAPI, decorators are typically applied when adding features like 인증, 인가, or 로깅. A common thread is that they are generally used when you need to create a shared module that runs across all APIs.
In this post, I will explain using the example in Flask.
You can implement authentication logic to verify a user’s identity. Before the API handler function executes, you can perform authentication checks (e.g., API key, JWT token, etc.). If the user is not authenticated, you can handle a 401 error within the decorator and return early.
The following is an example of JWT-based authentication logic.
pythonimport jwt from flask import Flask, request, jsonify from functools import wraps app = Flask(__name__) SECRET_KEY = app.config["SECRET_KEY"] def require_jwt(func): """JWT 토큰을 검증하는 데코레이터""" @wraps(func) def wrapper(*args, **kwargs): auth_header = request.headers.get("Authorization") if not auth_header or not auth_heade r.startswith("Bearer "): return jsonify({"error": "Missing or invalid Authorization header"}), 401 token = auth_header.split(" ")[1] try: decoded_token = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) except jwt.ExpiredSignatureError: return jsonify({"error": "Token has expired"}), 401 except jwt.InvalidTokenError: return jsonify({"error": "Invalid token"}), 401 # func이 실행되기 전에 위의 인증 과정을 먼저 거쳐 # 미인증된 경우, 비즈니스 로직 시행 이전에 401 status code를 보내줄 수 있다. # 대상 함수(api 핸들러) 실행 return func(*args, **kwargs) return wrapper @app.route("/auth", methods=["POST"]) @require_jwt # 데코레이터 추가 def authenticate_user(): return jsonify({"message": "토큰 검증 완료!"}), 200 if __name__ == "__main__": app.run(debug=True)
flask_jwt_extended package provides decorators for sophisticated JWT control. The code above is just one example; using the package is more convenient for development and maintenance. (Unless significant customization is required)You can control API requests based on the user’s permissions. Similar to the authentication step, you can execute the logic in a decorator before the API handler function runs. If the user’s permissions do not match, you can return early before executing the business logic.
pythonfrom flask import Flask, request, jsonify from functools import wraps # id, name, role 컬럼으로 이뤄진 Users 모델이 존재한다고 가정 from src.models.user import Users app = Flask(__name__) def require_role(role): """사용자 역할(Role)을 확인하는 인가 데코레이터""" @wraps(func) def wrapper(*args, **kwargs): username = request.get_json()["user_id" user = Users.get(username) if not user: return jsonify({"error": "User not found."}), 403 if user.role != role: return jsonify({"error": f"Access denied. {role} role required."}), 403 # func이 실행되기 전에 위의 인증 과정을 먼저 거쳐 # 지정된 role이 다른 유저가 API를 요청한 경우, # 비즈니스 로직 시행 이전에 401 status code를 보내 줄 수 있다. # 대상 함수(api 핸들러) 실행 return func(*args, **kwargs) return wrapper @app.route("/admin", methods=["GET"]) @require_role("admin") # admin 유저만 호출할 수 있게 데코레이터 추가 def check_user_is_admin(): return jsonify({"message": "Welcome, Admin!"}), 200 @app.route("/edit", methods=["GET"]) @require_role("editor") # editor 유저만 호출할 수 있게 데코레이터 추가 def edit_content(): return jsonify({"message": "You can edit content!"}), 200 if __name__ == "__main__": app.run(debug=True)
python@app.route("/admin", methods=["GET"]) @require_jwt # 인증 데코레이터 @require_role("admin") # 인가 데코레이터 def check_user_is_admin(): return jsonify({"message": "Welcome, Admin!"}), 200 ``` ### Logging You can also log API access logs. Before and after the business logic is executed via the API handler, you can collect both request and response information. ```python from flask import Flask, request from functools import wraps app = Flask(__name__) def logging_access(func): """요청이 들어오면 엔드포인트와 데이터를 로깅하는 데코레이터""" @wraps(func) def wrapper(*args, **kwargs): """예시에서는 단순 print를 썼지만, logger로 sysout 표출하거나 db 적재 로직을 함께 추가할 수 있다""" print(f"요청: {request.method} {request.path}") # 요청 메서드 및 경로 print(f"요청 데이터: {request.args.to_dict()}") # 쿼리 파라미터 확인 response = func(*args, **kwargs) # 대상 함수(api 핸들러) 실행 # response의 경우에는 func이 먼저 실행되고 난 후 print(f"응답 코드: {response.status_code}") return response # 핸들러 실행 결과는 꼭 return 해줘야 올바르게 된다. return wrapper @app.route("/", methods=["GET"]) @logging_access def hello(): return "Hello, Flask!" if __name__ == "__main__": app.run(debug=True)
In this post, I explained decorators, a fundamental yet fascinating feature of Python.
I also introduced how using decorators can make it easier to implement various logic when developing servers with Python.
Of course, indiscriminate use of decorators can create dependencies between them, which may actually increase complexity; therefore, they should be used appropriately to eliminate code duplication.
Since using decorators improves code readability and aids in maintenance, making them an efficient feature, I highly recommend their use.