พารามิเตอร์การกรองและการเรียงลำดับแบบไดนามิก
สร้างโมเดลพารามิเตอร์คำค้นหาที่นำกลับมาใช้ซ้ำได้สำหรับการกรอง การเรียงลำดับ และการเลือกฟิลด์พร้อมการตรวจสอบ
พารามิเตอร์การกรองและการเรียงลำดับแบบไดนามิก เป็นบทเรียน FastAPI Backend Development Bootcamp ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน FastAPI Backend Development Bootcamp และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส FastAPI Backend Development Bootcamp มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดจึงต้องใช้พารามิเตอร์คำสั่งแบบไดนามิก
ปลายทางสำหรับรายการในโลกจริงแทบไม่ส่งคืนข้อมูลทั้งหมด ไคลเอ็นต์ต้องการกรอง (เฉพาะผู้ใช้ที่ใช้งานอยู่) เรียงลำดับ (รายการใหม่สุดก่อน) และเลือกฟิลด์ (เฉพาะ id และชื่อ) หากเขียนกำหนดทุกชุดความเป็นไปได้ไว้ตายตัว จำนวนเส้นทางจะเพิ่มขึ้นอย่างรวดเร็ว
แนวทางที่สะอาดกว่าคือสร้างพารามิเตอร์คำสั่งเหล่านี้เป็นออบเจ็กต์ที่นำกลับมาใช้ใหม่ได้และผ่านการตรวจสอบค่า แล้วฉีดเข้าไปในปลายทางหลายแห่ง ในบทเรียนนี้เราจะสร้าง:
- โมเดลตัวกรองที่แปลงพารามิเตอร์คำสั่งเป็นเงื่อนไขที่ปลอดภัย
- ตัวแยกวิเคราะห์การเรียงลำดับพร้อมรายการอนุญาตของฟิลด์และทิศทาง
- กลไกการเลือกฟิลด์เพื่อลดขนาดข้อมูลผลลัพธ์
ทุกอย่างขับเคลื่อนด้วย dependency ของ FastAPI จึงยังคงเป็นไปตามหลัก DRY และทดสอบได้
การรวบรวมตัวกรองด้วยคลาส dependency
คลาสที่มี __init__ ซึ่งรับพารามิเตอร์ Query จะกลายเป็น dependency ที่นำกลับมาใช้ใหม่ได้ FastAPI จะอ่านพารามิเตอร์แต่ละตัวจาก URL และจัดทำเอกสารไว้ใน OpenAPI โดยอัตโนมัติ
ใช้ Optional[...] = None เพื่อให้ตัวกรองเป็นตัวเลือก: หากไม่มีพารามิเตอร์ ก็หมายความว่า “ไม่ต้องกรองคอลัมน์นี้”
from typing import Optional
from fastapi import Query
class UserFilterParams:
def __init__(
self,
status: Optional[str] = Query(None, description="active | inactive"),
min_age: Optional[int] = Query(None, ge=0, le=150),
search: Optional[str] = Query(None, min_length=2, max_length=50),
):
self.status = status
self.min_age = min_age
self.search = search
# Usage:
# @app.get('/users')
# def list_users(filters: UserFilterParams = Depends()):
# ...การตรวจสอบค่าตัวกรองด้วย Enum
ตัวกรองข้อความอิสระอย่าง status=foo เปิดทางให้ข้อมูลนำเข้าที่ไม่ถูกต้องผ่านเข้ามาได้ ให้จำกัดค่าด้วย Enum ที่อิงจาก str: FastAPI จะปฏิเสธค่าที่อยู่นอกชุดที่อนุญาต และแสดงรายการแบบเลื่อนลงในเอกสาร
นี่คือแนวป้องกันด่านแรก ค่าตัวกรองที่ไม่ถูกต้องจะได้รับข้อผิดพลาด 422 ที่ชัดเจน แทนที่จะรั่วไหลไปยังชั้นคำสั่งค้นข้อมูล
from enum import Enum
from typing import Optional
from fastapi import Query
class UserStatus(str, Enum):
active = "active"
inactive = "inactive"
pending = "pending"
class UserFilterParams:
def __init__(
self,
status: Optional[UserStatus] = Query(None),
min_age: Optional[int] = Query(None, ge=0, le=150),
):
self.status = status
self.min_age = min_ageการเปลี่ยนตัวกรองให้เป็นเพรดิเคต
แยกชั้น HTTP ออกจากชั้นข้อมูลออกจากกัน dependency มีหน้าที่เพียงรวบรวมและตรวจสอบค่า ส่วนตัวช่วยขนาดเล็กจะแปลงออบเจ็กต์ที่มีข้อมูลแล้วให้เป็นเพรดิเคตสำหรับกรองจริง
ต่อไปนี้เป็นเวอร์ชันที่ไม่ขึ้นกับเฟรมเวิร์ก ซึ่งคุณสามารถเรียกใช้กับดิกชันนารีทั่วไปได้ รูปแบบเดียวกันนี้สามารถนำไปใช้กับการเรียก .filter() ของ SQLAlchemy ได้อย่างราบรื่นในภายหลัง
USERS = [
{"id": 1, "name": "Ada", "status": "active", "age": 36},
{"id": 2, "name": "Linus", "status": "inactive", "age": 54},
{"id": 3, "name": "Grace", "status": "active", "age": 41},
]
def apply_filters(rows, status=None, min_age=None, search=None):
result = rows
if status is not None:
result = [r for r in result if r["status"] == status]
if min_age is not None:
result = [r for r in result if r["age"] >= min_age]
if search is not None:
result = [r for r in result if search.lower() in r["name"].lower()]
return result
print(apply_filters(USERS, status="active", min_age=40))การแยกวิเคราะห์พารามิเตอร์การเรียงลำดับ
สัญญาที่ใช้กันทั่วไปคือ ?sort=-created_at,name ซึ่งเป็นรายการคั่นด้วยจุลภาค โดยเครื่องหมาย - นำหน้าหมายถึงเรียงจากมากไปน้อย ให้แยกวิเคราะห์เป็นทูเพิล (field, direction)
อย่าเชื่อชื่อฟิลด์จากไคลเอ็นต์โดยไม่ตรวจสอบ ตรวจสอบแต่ละฟิลด์กับรายการอนุญาต เพื่อป้องกันไม่ให้ผู้ใช้เรียงลำดับหรือตรวจสอบคอลัมน์ใด ๆ ตามใจ
ALLOWED_SORT = {"created_at", "name", "age", "id"}
def parse_sort(sort_param):
parsed = []
for token in sort_param.split(","):
token = token.strip()
if not token:
continue
descending = token.startswith("-")
field = token[1:] if descending else token
if field not in ALLOWED_SORT:
raise ValueError(f"Cannot sort by '{field}'")
parsed.append((field, "desc" if descending else "asc"))
return parsed
print(parse_sort("-created_at,name"))
print(parse_sort("age"))Dependency สำหรับการเรียงลำดับที่นำกลับมาใช้ใหม่ได้
ห่อตัวแยกวิเคราะห์ไว้ใน dependency เพื่อให้ปลายทางรายการทุกแห่งใช้สัญญาและการตรวจสอบการเรียงลำดับแบบเดียวกัน การส่ง HTTPException(422) เมื่อฟิลด์ไม่ถูกต้องจะให้ข้อผิดพลาดที่แม่นยำและเครื่องอ่านได้แก่ไคลเอ็นต์
การส่งรายการอนุญาตเข้าไปทำให้ dependency นี้นำกลับมาใช้ซ้ำกับทรัพยากรที่มีคอลัมน์สำหรับเรียงลำดับแตกต่างกันได้
from typing import Optional
from fastapi import Query, HTTPException
def sort_dependency(allowed: set):
def _parse(sort: Optional[str] = Query(None, example="-created_at,name")):
if not sort:
return []
parsed = []
for token in sort.split(","):
token = token.strip()
if not token:
continue
desc = token.startswith("-")
field = token[1:] if desc else token
if field not in allowed:
raise HTTPException(422, f"Invalid sort field: {field}")
parsed.append((field, "desc" if desc else "asc"))
return parsed
return _parse
# @app.get('/users')
# def list_users(sort=Depends(sort_dependency({'created_at','name'}))):
# ...การเรียงลำดับหลายคีย์ในหน่วยความจำ
ต้องใช้คีย์สำหรับเรียงลำดับหลายตัวตามลำดับ วิธีที่คงที่คือเรียงด้วยคีย์ที่มีนัยสำคัญน้อยที่สุดก่อน แล้วทำย้อนกลับ เพราะ sorted ของ Python รักษาลำดับเดิมเมื่อค่าเท่ากัน
ตัวอย่างแบบทำงานได้ในตัวเองนี้ให้ผลลัพธ์สอดคล้องกับสิ่งที่ฐานข้อมูลจะสร้างจาก ORDER BY a, b DESC
ROWS = [
{"name": "Ada", "age": 36},
{"name": "Grace", "age": 36},
{"name": "Linus", "age": 54},
]
def apply_sort(rows, sort_keys):
result = list(rows)
for field, direction in reversed(sort_keys):
result.sort(key=lambda r: r[field], reverse=(direction == "desc"))
return result
ordered = apply_sort(ROWS, [("age", "desc"), ("name", "asc")])
for r in ordered:
print(r)การเลือกฟิลด์ (ชุดฟิลด์แบบกระชับ)
หากต้องการลดขนาดข้อมูล ให้รองรับ ?fields=id,name ไคลเอ็นต์จะเลือกคีย์ที่จะส่งกลับได้ แต่เช่นเคย ต้องตรวจสอบกับรายการอนุญาตของฟิลด์ที่เปิดเผยได้ เพื่อไม่ให้มีการร้องขอคอลัมน์ภายใน เช่น password_hash
การเลือกเป็นขั้นตอนการฉายข้อมูลที่ทำหลังจากการกรองและการเรียงลำดับ และทำทันทีก่อนการแปลงเป็นข้อมูลอนุกรม
EXPOSABLE = {"id", "name", "status", "age"}
def select_fields(rows, fields_param):
if not fields_param:
return rows
requested = {f.strip() for f in fields_param.split(",") if f.strip()}
invalid = requested - EXPOSABLE
if invalid:
raise ValueError(f"Unknown fields: {sorted(invalid)}")
return [{k: r[k] for k in requested if k in r} for r in rows]
data = [{"id": 1, "name": "Ada", "status": "active", "age": 36}]
print(select_fields(data, "id,name"))การรวมการกรอง การเรียงลำดับ การเลือก และการแบ่งหน้า
ลำดับของกระบวนการมีความสำคัญต่อความถูกต้องและประสิทธิภาพ: กรองก่อนเพื่อลดชุดข้อมูล จากนั้นเรียงลำดับ แล้วแบ่งหน้า (ตัดข้อมูล) และสุดท้ายเลือกฟิลด์เฉพาะในหน้าที่จะส่งคืน
การเลือกฟิลด์ก่อนแบ่งหน้าจะยังคงต้องสแกนข้อมูลทั้งหมด และการแบ่งหน้าก่อนเรียงลำดับจะทำให้ได้หน้าที่ไม่ถูกต้อง
def list_resource(rows, *, filters, sort_keys, fields, offset, limit,
apply_filters, apply_sort, select_fields):
rows = apply_filters(rows, **filters)
rows = apply_sort(rows, sort_keys)
total = len(rows)
page = rows[offset: offset + limit]
page = select_fields(page, fields)
return {"total": total, "items": page,
"offset": offset, "limit": limit}
# In FastAPI each piece is a Depends(); the route just calls list_resource.การประกอบ dependency เป็นออบเจ็กต์คำสั่งเดียว
แทนที่จะส่ง dependency สี่ตัวแยกกันเข้าไปในทุกเส้นทาง ให้ประกอบรวมกัน dependency ตัวห่อหุ้มสามารถส่งคืนออบเจ็กต์ที่เป็นระเบียบหนึ่งตัว ซึ่งเก็บตัวกรอง คีย์การเรียงลำดับ ฟิลด์ และการแบ่งหน้าไว้
วิธีนี้ทำให้ลายเซ็นของเส้นทางสั้นลง และมีจุดเดียวสำหรับปรับขยายสัญญาของคำสั่ง
from dataclasses import dataclass
from typing import Optional
from fastapi import Depends, Query
@dataclass
class ListQuery:
filters: object
sort: list
fields: Optional[str]
offset: int
limit: int
def list_query(
filters: "UserFilterParams" = Depends(),
sort: list = Depends(sort_dependency({"created_at", "name"})),
fields: Optional[str] = Query(None),
offset: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
) -> ListQuery:
return ListQuery(filters, sort, fields, offset, limit)
# @app.get('/users')
# def list_users(q: ListQuery = Depends(list_query)):
# ...การจัดทำเอกสารและกำหนดค่าเริ่มต้นให้สัญญา
สัญญาของคำค้นที่ดีควร อธิบายตัวเองได้ และ ปลอดภัยตั้งแต่ค่าเริ่มต้น:
- กำหนด
descriptionและexampleให้กับQueryทุกตัว เพื่อให้เอกสาร OpenAPI อธิบายไวยากรณ์ได้ - จำกัดค่า
limitด้วยle=100เพื่อไม่ให้ไคลเอ็นต์ร้องขอข้อมูลเป็นล้านแถว - เลือกการเรียงลำดับเริ่มต้นที่เหมาะสม เช่น ใหม่ที่สุดก่อน เพื่อให้ผลลัพธ์ แน่นอน ในทุกหน้า
- ปฏิเสธฟิลด์หรือคีย์การเรียงลำดับที่ไม่รู้จักด้วย 422 แทนการละเว้นโดยไม่แจ้งให้ทราบ
การจัดลำดับให้แน่นอนเป็นสิ่งสำคัญอย่างยิ่ง หากไม่มีการเรียงลำดับที่คงที่ การแบ่งหน้าอาจแสดงแถวซ้ำหรือข้ามบางแถวระหว่างคำขอได้
ตรวจสอบด่วน: ลำดับของไปป์ไลน์
คุณเปิดให้ใช้งาน GET /products ซึ่งรองรับการกรอง การเรียงลำดับ การแบ่งหน้า และชุดฟิลด์แบบเลือกบางส่วน ควรใช้การดำเนินการเหล่านี้ตามลำดับใดเพื่อส่งคืนหน้าที่ถูกต้องอย่างมีประสิทธิภาพ
สรุป
คุณสร้างชั้นคำค้นที่ใช้ซ้ำได้และผ่านการตรวจสอบสำหรับจุดปลายรายการของ FastAPI:
- ตัวกรอง ในรูปคลาส dependency พร้อมพารามิเตอร์
Optionalและการตรวจสอบด้วยEnum/ข้อจำกัด - การเรียงลำดับ แยกวิเคราะห์จากไวยากรณ์
-field,fieldโดยตรวจสอบกับรายการที่อนุญาต และส่งข้อผิดพลาด 422 เมื่อพบฟิลด์ที่ไม่รู้จัก - การเลือกฟิลด์ (ชุดฟิลด์แบบเลือกบางส่วน) จำกัดไว้เฉพาะรายการที่อนุญาตให้เปิดเผยได้ เพื่อปกป้องคอลัมน์ภายใน
- dependency
ListQueryที่ประกอบขึ้นเพื่อให้ลายเซ็นของเส้นทางดูสะอาด
โปรดจำไปป์ไลน์: กรอง → เรียงลำดับ → แบ่งหน้า → เลือก โดยใช้การเรียงลำดับเริ่มต้นที่แน่นอนเสมอ เพื่อให้การแบ่งหน้าสอดคล้องกัน รูปแบบเหล่านี้นำไปใช้กับคำค้นของ SQLAlchemy ได้โดยตรง เมื่อคุณเปลี่ยนจากข้อมูลในหน่วยความจำไปใช้ฐานข้อมูลจริง
เรียนรู้ FastAPI Backend Development Bootcamp ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 21
- บทเรียน
- 84
คำถามที่พบบ่อย
บทเรียน “พารามิเตอร์การกรองและการเรียงลำดับแบบไดนามิก” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “พารามิเตอร์การกรองและการเรียงลำดับแบบไดนามิก” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส FastAPI Backend Development Bootcamp ให้อัปเกรดเป็น CoddyKit PRO คอร์ส FastAPI Backend Development Bootcamp มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “พารามิเตอร์การกรองและการเรียงลำดับแบบไดนามิก”
สร้างโมเดลพารามิเตอร์คำค้นหาที่นำกลับมาใช้ซ้ำได้สำหรับการกรอง การเรียงลำดับ และการเลือกฟิลด์พร้อมการตรวจสอบ คุณปฏิบัติ FastAPI Backend Development Bootcamp ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน FastAPI Backend Development Bootcamp หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน FastAPI Backend Development Bootcamp บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “พารามิเตอร์การกรองและการเรียงลำดับแบบไดนามิก” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน FastAPI Backend Development Bootcamp นี้ได้ไหม
ได้ บทเรียน FastAPI Backend Development Bootcamp ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การกำหนดเวอร์ชันด้วย URL ส่วนหัว และชนิดสื่อ
- การแบ่งหน้าแบบเคอร์เซอร์เทียบกับออฟเซ็ตในระบบขนาดใหญ่
- พารามิเตอร์การกรองและการเรียงลำดับแบบไดนามิก
- การออกแบบซองหุ้มการตอบกลับที่เสถียร