Ở Bài 1, chúng ta đã làm quen với FastAPI. Ở Bài 2, chúng ta đã ôn lại nền tảng Python cơ bản. Bây giờ là lúc đi sâu vào ba chủ đề Python nâng cao mà bất kỳ lập trình viên FastAPI nghiêm túc nào cũng cần hiểu: asyncio, decorators, và metaclasses.
Tại sao ba chủ đề này lại quan trọng đến vậy? Vì chúng chính là nền móng mà FastAPI được xây dựng:
Khi bạn viết async def cho một endpoint — đó là asyncio. Khi bạn dùng @app.get("/users") — đó là decorators. Khi bạn kế thừa BaseModel từ Pydantic và tất cả validation tự động “thần kỳ” xuất hiện — đó là metaclasses đang làm việc sau hậu trường.
Hiểu rõ ba chủ đề này, bạn sẽ không chỉ “biết dùng” FastAPI mà còn hiểu “tại sao” nó hoạt động như vậy. Bắt đầu thôi! 😎
Phần 1: Asyncio — Lập trình bất đồng bộ trong Python
Tại sao cần async?
Hãy tưởng tượng bạn xây dựng một API endpoint cần gọi đến database và một dịch vụ bên ngoài. Với code đồng bộ truyền thống:
import time
def get_user_data(user_id: int):
# Giả lập gọi database - mất 1 giây
time.sleep(1)
return {"id": user_id, "name": "Joseph"}
def get_user_posts(user_id: int):
# Giả lập gọi API ngoài - mất 2 giây
time.sleep(2)
return ["Post 1", "Post 2"]
# Tổng thời gian: 3 giây (chạy tuần tự)
start = time.time()
user = get_user_data(1)
posts = get_user_posts(1)
print(f"Xong sau {time.time() - start:.2f}s") # 3.00s
Trong 3 giây đó, CPU của bạn gần như không làm gì — nó chỉ ngồi chờ database và API trả kết quả về. Đây là sự lãng phí khổng lồ, đặc biệt khi API của bạn phải xử lý hàng nghìn request cùng lúc.
Async cho phép bạn nói: “Trong lúc chờ database, hãy làm việc khác đi.” Đây chính là lý do FastAPI nhanh đến vậy.
Cú pháp async/await cơ bản
import asyncio
async def get_user_data(user_id: int):
# asyncio.sleep thay vì time.sleep
await asyncio.sleep(1)
return {"id": user_id, "name": "Joseph"}
async def get_user_posts(user_id: int):
await asyncio.sleep(2)
return ["Post 1", "Post 2"]
async def main():
# Chạy song song với gather
user, posts = await asyncio.gather(
get_user_data(1),
get_user_posts(1)
)
print(user, posts)
# Chạy
import time
start = time.time()
asyncio.run(main())
print(f"Xong sau {time.time() - start:.2f}s") # 2.00s (thay vì 3.00s!)
Hai điểm quan trọng cần nhớ:
Một: async def định nghĩa một coroutine — không phải function thông thường. Khi bạn gọi nó, bạn nhận được một coroutine object, không phải kết quả. Bạn phải await nó để lấy kết quả.
Hai: await chỉ dùng được bên trong async def. Nó nói với Python: “Dừng coroutine này lại, cho phép event loop chạy việc khác, khi nào có kết quả thì quay lại.”
async trong FastAPI
FastAPI hỗ trợ cả sync và async endpoints. Khi nào nên dùng gì?
from fastapi import FastAPI
import asyncio
import httpx
app = FastAPI()
# Dùng async khi có I/O bất đồng bộ (database, HTTP call, file...)
@app.get("/users/{user_id}")
async def get_user(user_id: int):
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.example.com/users/{user_id}")
return response.json()
# Dùng sync khi code CPU-bound hoặc không có I/O bất đồng bộ
@app.get("/calculate/{n}")
def calculate(n: int):
return {"result": sum(i * i for i in range(n))}
Quy tắc đơn giản: nếu code của bạn có await, dùng async def. Nếu không, dùng def thường. FastAPI đủ thông minh để chạy sync function trong thread pool riêng nên không block event loop.
Cẩn thận: đừng dùng blocking call trong async function
# SAI - time.sleep là blocking, sẽ block cả event loop
@app.get("/bad")
async def bad_endpoint():
import time
time.sleep(5) # Chết cả server trong 5 giây!
return {"status": "ok"}
# ĐÚNG - dùng asyncio.sleep
@app.get("/good")
async def good_endpoint():
await asyncio.sleep(5)
return {"status": "ok"}
# Nếu bắt buộc dùng thư viện blocking (sync), chạy trong thread riêng
from fastapi.concurrency import run_in_threadpool
@app.get("/legacy")
async def legacy_endpoint():
result = await run_in_threadpool(blocking_function)
return result
Phần 2: Decorators — “Phép thuật” đứng sau @app.get
Function là first-class citizen
Để hiểu decorator, trước tiên cần hiểu: trong Python, function là object. Bạn có thể gán nó cho biến, truyền vào function khác, hoặc trả về từ function.
def greet(name: str) -> str:
return f"Xin chào, {name}!"
# Gán function cho biến
say_hi = greet
print(say_hi("Joseph")) # Xin chào, Joseph!
# Truyền function vào function khác
def execute(func, arg):
return func(arg)
print(execute(greet, "Alice")) # Xin chào, Alice!
Decorator là gì?
Decorator là một function nhận vào một function và trả về một function khác (thường là phiên bản “được trang trí” của function gốc).
def my_decorator(func):
def wrapper(*args, **kwargs):
print(f"Trước khi gọi {func.__name__}")
result = func(*args, **kwargs)
print(f"Sau khi gọi {func.__name__}")
return result
return wrapper
@my_decorator
def say_hello(name: str):
print(f"Hello, {name}!")
say_hello("Joseph")
# Trước khi gọi say_hello
# Hello, Joseph!
# Sau khi gọi say_hello
Cú pháp @my_decorator thực ra chỉ là đường tắt cho:
def say_hello(name: str):
print(f"Hello, {name}!")
say_hello = my_decorator(say_hello)
Decorator thực tế: đo thời gian thực thi
import time
from functools import wraps
def measure_time(func):
@wraps(func) # Giữ lại metadata của function gốc
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"{func.__name__} chạy mất {elapsed:.4f}s")
return result
return wrapper
@measure_time
def slow_function():
time.sleep(1)
return "Xong!"
slow_function()
# slow_function chạy mất 1.0012s
Chú ý @wraps(func) — nó quan trọng vì giữ lại __name__, __doc__, và các metadata khác của function gốc. Nếu không có nó, slow_function.__name__ sẽ trả về "wrapper" — gây khó khăn cho debugging và tài liệu.
Decorator có tham số
Đôi khi bạn muốn decorator nhận tham số, ví dụ @retry(times=3). Lúc này cần thêm một lớp function nữa:
from functools import wraps
def retry(times: int = 3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_error = None
for attempt in range(times):
try:
return func(*args, **kwargs)
except Exception as e:
last_error = e
print(f"Lần {attempt + 1} thất bại: {e}")
raise last_error
return wrapper
return decorator
@retry(times=3)
def unstable_api_call():
import random
if random.random() < 0.7:
raise ConnectionError("Mạng lag!")
return "Thành công"
print(unstable_api_call())
FastAPI và decorators
Bây giờ khi nhìn vào code FastAPI, bạn sẽ hiểu tại sao nó hoạt động như vậy:
from fastapi import FastAPI
app = FastAPI()
# @app.get("/") là một decorator có tham số!
# Nó đăng ký function read_root vào routing table của app
# với method GET và path "/"
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.post("/users")
def create_user(name: str):
return {"name": name}
Decorator @app.get("/") không chỉ “đánh dấu” function — nó thực sự đăng ký function đó vào hệ thống routing của FastAPI. Khi có request đến, FastAPI tra cứu bảng này để biết gọi function nào.
Phần 3: Metaclasses — Class của class
Trong Python, mọi thứ đều là object
Đây là khái niệm khó nhất trong bài. Hãy bắt đầu từ cơ bản: trong Python, class cũng là object. Vậy nếu class là object, thì nó phải được tạo ra từ một “class” khác — đó chính là metaclass.
class User:
pass
# User là một object
print(type(User)) # <class 'type'>
# type chính là metaclass mặc định của Python!
# User là instance của type
user = User()
print(type(user)) # <class '__main__.User'>
print(type(type(user))) # <class 'type'>
Nói ngắn gọn: metaclass là class dùng để tạo ra class khác. Giống như class là “template” tạo ra instance, metaclass là “template” tạo ra class.
Tạo class bằng type() trực tiếp
# Cách thông thường
class User:
def greet(self):
return f"Hi, I'm {self.name}"
# Tương đương với cách dùng type() trực tiếp
def greet(self):
return f"Hi, I'm {self.name}"
User = type("User", (), {"greet": greet})
# Cả hai đều hoạt động giống hệt
u = User()
u.name = "Joseph"
print(u.greet()) # Hi, I'm Joseph
Tạo metaclass tuỳ chỉnh
Metaclass tuỳ chỉnh cho phép bạn can thiệp vào quá trình tạo class — thêm/bớt thuộc tính, validate, đăng ký class vào registry, v.v.
class UppercaseAttributesMeta(type):
"""Metaclass tự động chuyển tên attribute sang UPPERCASE"""
def __new__(mcs, name, bases, attrs):
uppercase_attrs = {}
for attr_name, attr_value in attrs.items():
if not attr_name.startswith("__"):
uppercase_attrs[attr_name.upper()] = attr_value
else:
uppercase_attrs[attr_name] = attr_value
return super().__new__(mcs, name, bases, uppercase_attrs)
class Config(metaclass=UppercaseAttributesMeta):
database_url = "postgres://localhost"
debug = True
print(Config.DATABASE_URL) # postgres://localhost
print(Config.DEBUG) # True
# print(Config.database_url) # AttributeError!
Ví dụ thực tế: Auto-registry pattern
Đây là use case rất phổ biến — tự động đăng ký mọi subclass vào một registry:
class PluginMeta(type):
registry = {}
def __new__(mcs, name, bases, attrs):
cls = super().__new__(mcs, name, bases, attrs)
# Không đăng ký base class
if bases:
mcs.registry[name] = cls
return cls
class Plugin(metaclass=PluginMeta):
def run(self):
raise NotImplementedError
class EmailPlugin(Plugin):
def run(self):
return "Gửi email"
class SMSPlugin(Plugin):
def run(self):
return "Gửi SMS"
# Mọi plugin được tự động đăng ký!
print(PluginMeta.registry)
# {'EmailPlugin': <class 'EmailPlugin'>, 'SMSPlugin': <class 'SMSPlugin'>}
for name, plugin_cls in PluginMeta.registry.items():
print(f"{name}: {plugin_cls().run()}")
Pydantic và metaclasses
Đây là điều “thần kỳ” đằng sau Pydantic BaseModel (và do đó, đằng sau toàn bộ hệ thống validation của FastAPI). Khi bạn viết:
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
is_active: bool = True
# Pydantic tự động:
# - Validate kiểu dữ liệu
# - Tạo method .dict(), .json()
# - Tạo JSON schema
# - Parse từ dict: User(**data)
user = User(id=1, name="Joseph", email="joseph@example.com")
print(user.dict())
Tất cả “phép thuật” trên không phải từ BaseModel — mà từ metaclass của BaseModel (ModelMetaclass). Khi Python tạo class User, metaclass này đọc các type annotations (id: int, name: str…), tạo ra validators tương ứng, và gắn chúng vào class.
Đây là lý do tại sao FastAPI có thể tự động validate request body, tạo Swagger docs, và convert JSON ↔ Python object một cách “thần kỳ”.
Khi nào nên dùng metaclass?
Một câu nói nổi tiếng của Tim Peters (tác giả “The Zen of Python”):
“Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t.”
Trong thực tế, 99% trường hợp bạn sẽ không cần tự viết metaclass. Nhưng hiểu cách chúng hoạt động giúp bạn:
Đọc hiểu code của các framework lớn như Pydantic, SQLAlchemy, Django ORM. Biết debug khi có vấn đề liên quan đến class creation. Thiết kế API thư viện tốt hơn khi cần. Tự tin hơn khi làm việc với FastAPI vì hiểu được “bên trong” nó đang làm gì.
Tổng kết
Chúng ta vừa đi qua ba chủ đề Python nâng cao rất quan trọng:
- Asyncio — nền tảng cho hiệu suất cao của FastAPI. Biết khi nào dùng
async def, khi nào dùngdef, và tránh blocking calls trong async code. - Decorators — cơ chế đằng sau cú pháp
@app.get("/"). Hiểu decorators giúp bạn tạo các middleware, logging, authentication decorators của riêng mình trong tương lai. - Metaclasses — “phép thuật” đứng sau Pydantic BaseModel. Bạn không cần tự viết metaclass, nhưng hiểu chúng giúp bạn hiểu sâu cách FastAPI validation hoạt động.
Ở Bài 4 tiếp theo, chúng ta sẽ quay lại với thực tế và xây dựng nền tảng kiến thức vững chắc về API và HTTP — những khái niệm cốt lõi mà bất kỳ ai xây dựng web service cũng phải nắm vững: methods, status codes, headers, REST principles, và nhiều hơn nữa.
