跳到主要內容

Web Day 10 錯誤處理與 API 回應設計

Web Day 10 錯誤處理與 API 回應設計

執行需求:CPU 可跑。昨天的遷移管理把資料庫結構變更變成可控的流程,但應用程式跑起來之後,「使用者送進來的請求不可能永遠合法」這件事還沒處理。今天我們要設計一套「統一例外類別 + 全域處理器 + 標準化錯誤回應」的架構,讓每個端點在各種錯誤情境下都能回傳結構一致的 JSON,並且讓前端、開發者、維運人員都能從錯誤回應裡快速判斷發生了什麼事。

引言

到目前為止,我們的範例多半專注在「成功路徑」:使用者送對請求、伺服器回傳 200 與資料。但真實世界的 API 面對的是「使用者送出錯誤的 ID、缺欄位、權限不足、外部服務掛了、資料庫連不到」這堆麻煩事。如果每個端點都自己處理錯誤,最後會出現三種亂象:第一,每個端點回傳的錯誤結構不同,前端要寫一堆 if/else;第二,有些錯誤沒被處理,直接把 Python 的 traceback 丟給使用者(這是非常嚴重的資安問題);第三,HTTP 狀態碼用得很隨意,例如「找不到資料」回 200 但 body 帶錯誤訊息,讓監控系統誤判。

今天的目標是建立一套「FastAPI 錯誤處理」的標準模式。讀完之後,你會學到:自訂例外類別的設計原則、exception_handler 的註冊方式、如何把 Pydantic 驗證錯誤、SQLModel 找不到資料、未預期例外三種情況統一成一致的 JSON 結構,並且知道「5xx 錯誤不要洩漏內部細節」這個資安基本原則。這套架構會在後續的認證(Web Day 12–13)與專案篇(Web Day 35+)持續沿用,今天的程式碼會成為整個系列的基礎之一。

為什麼需要標準化錯誤回應

在談怎麼做之前,先談為什麼這樣做。一個好的錯誤回應要滿足四個條件。第一,「結構一致」:所有錯誤都帶有相同的 JSON 欄位,例如 code、message、details,前端只要寫一支通用解析函式就能處理。第二,「HTTP 狀態碼正確」:找不到資料回 404、權限不足回 403、驗證失敗回 422、伺服器內部錯誤回 500,這些是 HTTP 規範的基本要求,不能亂給。第三,「錯誤訊息友善」:對外(給使用者)的訊息要避免暴露內部細節,例如資料表名稱、檔案路徑、SQL 指令;對內(給開發者)的訊息要能定位問題,通常會另外寫進伺服器 log。第四,「可追蹤」:每個錯誤回應帶一個唯一的 request id 或 trace id,前端出問題時能貼給開發者,開發者就能在 log 裡找到對應紀錄。

FastAPI 內建支援上述大部分需求:HTTPException 可以丟出帶狀態碼的例外、RequestValidationError 會自動把 Pydantic 驗證失敗的細節包成 422 回應、global exception handler 可以攔截所有未處理的例外並回傳統一結構。我們今天的設計就是圍繞這三個機制展開。

很多團隊會選擇「錯誤時一律回 200,body 帶錯誤碼」這種設計,這在內部系統還可以,但在對外 API 會出問題:很多監控與快取系統(例如 CDN、API gateway、客戶端的 retry 機制)會根據 HTTP 狀態碼決定行為,把所有錯誤都回成 200 會讓這些機制失靈。HTTP 規範之所以設計 4xx(使用者錯誤)與 5xx(伺服器錯誤)兩大類,就是為了讓中間層能正確處理。我們今天嚴格遵守這個原則。

錯誤回應的 JSON 結構

先把「目標樣貌」定義清楚。我們要讓所有錯誤回應長得像這樣:

{
  "error": {
    "code": "HERO_NOT_FOUND",
    "message": "找不到這位英雄",
    "details": null,
    "request_id": "f7e1..."
  }
}

四個欄位的意義:code 是機器可讀的錯誤代號,用底線分隔的全大寫英文,方便前端 switch;message 是給人看的中文訊息;details 是選填的補充資訊(例如 Pydantic 驗證失敗時的欄位清單);request_id 是這次請求的唯一識別碼,用來對應伺服器 log。當錯誤發生時,HTTP 狀態碼仍然照規範設定(例如 404、422、500),但 body 統一用這套結構。

定義這個結構的 Pydantic 模型:

# app/errors.py
# 統一的錯誤回應結構
from typing import Any, Optional

from pydantic import BaseModel


class ErrorBody(BaseModel):
    # error.code:機器可讀的錯誤代號
    code: str
    # error.message:給人看的訊息(中文)
    message: str
    # error.details:選填補充資訊
    details: Optional[Any] = None
    # error.request_id:對應伺服器 log 的識別碼
    request_id: Optional[str] = None


class ErrorResponse(BaseModel):
    error: ErrorBody

這個檔案放在 app/errors.py,裡面只放「錯誤的形狀」定義,不放實際的處理邏輯。把它放在獨立的模組是為了讓其他模組(例如下一篇的認證模組)也能 import 同一份定義,避免錯誤結構各處重複定義最後不一致。

自訂例外類別

FastAPI 的 HTTPException 雖然方便,但它把錯誤代號塞在 detail 裡,很難強型別地處理。我們自訂一套例外類別,把「錯誤代號」「HTTP 狀態碼」「給使用者的訊息」綁在一起:

# app/errors.py(接續上面)
from fastapi import HTTPException, status


class AppError(HTTPException):
    # 所有應用程式自訂例外的共同基底
    code: str = "INTERNAL_ERROR"
    message: str = "伺服器內部錯誤"

    def __init__(
        self,
        message: Optional[str] = None,
        details: Optional[Any] = None,
        headers: Optional[dict] = None,
    ) -> None:
        self.details = details
        super().__init__(
            status_code=self.status_code,
            detail=message or self.message,
            headers=headers,
        )

    @property
    def status_code(self) -> int:
        # 子類別覆寫這個屬性來設定 HTTP 狀態碼
        return status.HTTP_500_INTERNAL_SERVER_ERROR


class NotFoundError(AppError):
    code = "NOT_FOUND"
    message = "找不到資源"

    @property
    def status_code(self) -> int:
        return status.HTTP_404_NOT_FOUND


class ValidationFailedError(AppError):
    # 業務邏輯驗證失敗(與 Pydantic 結構驗證不同)
    code = "VALIDATION_FAILED"
    message = "輸入資料不符合業務規則"

    @property
    def status_code(self) -> int:
        return status.HTTP_422_UNPROCESSABLE_ENTITY


class ConflictError(AppError):
    # 重複註冊、唯一鍵衝突等情境
    code = "CONFLICT"
    message = "資源已存在或狀態衝突"

    @property
    def status_code(self) -> int:
        return status.HTTP_409_CONFLICT


class UnauthorizedError(AppError):
    # 未登入或 token 失效
    code = "UNAUTHORIZED"
    message = "需要登入"

    @property
    def status_code(self) -> int:
        return status.HTTP_401_UNAUTHORIZED


class ForbiddenError(AppError):
    # 已登入但權限不足
    code = "FORBIDDEN"
    message = "權限不足"

    @property
    def status_code(self) -> int:
        return status.HTTP_403_FORBIDDEN

這套設計把「例外類別」與「HTTP 回應」綁在一起:當你在端點裡 raise NotFoundError("找不到這位英雄"),FastAPI 看到的是 HTTPException(status_code=404, detail="找不到這位英雄");但因為我們等一下會註冊全域處理器,它會把 code 與 details 一起包進 JSON 回應。每個子類別只需要覆寫 code、message、status_code 三個屬性即可,使用方式非常一致。這種「集中定義錯誤型別」的做法在大團隊特別有用:新人只要看 errors.py 就知道整個系統有哪些錯誤,不需到處翻路由程式碼。

為什麼不直接用 HTTPException(status_code=404, detail="找不到")?因為這樣每個端點都要記得帶正確的狀態碼、寫一致的訊息,久了會出現「有人忘了帶 404」「有人寫了 404 但訊息是英文」這類不一致。自訂例外把這些決定集中在類別定義裡,新增錯誤類別也只需要加一支 class。

全域錯誤處理器

現在要把上面定義的例外與「統一 JSON 結構」接起來。在 FastAPI 裡,這件事用 exception_handler 裝飾器完成。建立 app/exception_handlers.py:

# app/exception_handlers.py
import logging
import uuid
from typing import Any

from fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from sqlalchemy.exc import IntegrityError
from starlette.exceptions import HTTPException as StarletteHTTPException

from app.errors import AppError, ErrorBody, ErrorResponse

logger = logging.getLogger(__name__)


def _build_error_response(
    code: str,
    message: str,
    status_code: int,
    request: Request,
    details: Any = None,
) -> JSONResponse:
    # 為這次請求產生一個識別碼(用來對應 log)
    request_id = getattr(request.state, "request_id", None) or uuid.uuid4().hex
    body = ErrorResponse(
        error=ErrorBody(
            code=code,
            message=message,
            details=details,
            request_id=request_id,
        )
    )
    return JSONResponse(
        status_code=status_code,
        content=jsonable_encoder(body),
    )


async def app_error_handler(
    request: Request, exc: AppError
) -> JSONResponse:
    # 處理自訂例外:狀態碼與訊息都從例外物件取
    logger.info(
        "AppError raised: code=%s status=%s message=%s",
        exc.code, exc.status_code, exc.detail,
    )
    return _build_error_response(
        code=exc.code,
        message=str(exc.detail),
        status_code=exc.status_code,
        request=request,
        details=exc.details,
    )


async def validation_error_handler(
    request: Request, exc: RequestValidationError
) -> JSONResponse:
    # 處理 Pydantic 結構驗證錯誤(422)
    logger.info("RequestValidationError: %s", exc.errors())
    return _build_error_response(
        code="INVALID_INPUT",
        message="請求內容驗證失敗",
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        request=request,
        details=exc.errors(),
    )


async def integrity_error_handler(
    request: Request, exc: IntegrityError
) -> JSONResponse:
    # 處理 SQLAlchemy/SQLModel 的唯一鍵衝突等錯誤
    logger.warning("IntegrityError: %s", exc.orig)
    return _build_error_response(
        code="CONFLICT",
        message="資料衝突,請檢查唯一鍵或外鍵",
        status_code=status.HTTP_409_CONFLICT,
        request=request,
    )


async def unhandled_exception_handler(
    request: Request, exc: Exception
) -> JSONResponse:
    # 處理所有未預期的例外:對外隱藏細節,只回通用訊息
    request_id = uuid.uuid4().hex
    logger.exception(
        "Unhandled exception (request_id=%s)", request_id
    )
    return _build_error_response(
        code="INTERNAL_ERROR",
        message="伺服器內部錯誤,請稍後再試",
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        request=request,
    )


def register_exception_handlers(app: FastAPI) -> None:
    # 把所有處理器註冊到 FastAPI 應用
    app.add_exception_handler(AppError, app_error_handler)
    app.add_exception_handler(RequestValidationError, validation_error_handler)
    app.add_exception_handler(IntegrityError, integrity_error_handler)
    app.add_exception_handler(StarletteHTTPException, app_error_handler)
    app.add_exception_handler(Exception, unhandled_exception_handler)

這支檔案有五個關鍵設計:第一,_build_error_response 是共用的小工具,負責組裝 JSON 結構與 HTTP 狀態碼;第二,app_error_handler 處理我們的自訂例外;第三,validation_error_handler 把 FastAPI 內建的 RequestValidationError 包成統一格式;第四,integrity_error_handler 把 SQLAlchemy 的 IntegrityError(唯一鍵衝突、外鍵約束)翻成 409;第五,unhandled_exception_handler 是最後一道防線,攔截所有沒被前面接住的例外,回 500 並把 traceback 寫進 log(logger.exception),但對外只給通用訊息,避免暴露內部細節。

注意 StarletteHTTPException 也掛上 app_error_handler,這會讓直接呼叫 raise HTTPException(status_code=404) 的程式碼也走同一套結構,確保任何 HTTP 錯誤都長一樣。

把處理器註冊到 FastAPI 應用

把 register_exception_handlers(app) 放進 main.py 的啟動流程:

# app/main.py
import logging
import uuid

from fastapi import FastAPI, Request

from app.exception_handlers import register_exception_handlers
from app.routers import heroes

# 基本 log 設定
logging.basicConfig(level=logging.INFO)

app = FastAPI(title="Web Day 10 範例", version="0.1.0")

# 註冊所有錯誤處理器
register_exception_handlers(app)


@app.middleware("http")
async def add_request_id(request: Request, call_next):
    # 為每個請求產生 request_id,方便對應 log
    request_id = uuid.uuid4().hex
    request.state.request_id = request_id
    response = await call_next(request)
    response.headers["X-Request-ID"] = request_id
    return response


# 註冊路由(Web Day 7 風格)
app.include_router(heroes.router, prefix="/api")

這段用 FastAPI 的 middleware 機制為每個請求加上一個 request_id,並放到 request.state,這樣所有處理器都能讀到。回應的 header 也帶一份(X-Request-ID),前端出問題時可以把這個 header 貼給開發者,開發者到 log 裡搜尋就能還原當時的情境。

在端點裡使用自訂例外

現在來看一個使用範例。建立 app/routers/heroes.py,裡面會示範「找不到資料」、「驗證失敗」、「衝突」三種情境:

# app/routers/heroes.py
from fastapi import APIRouter, Depends
from sqlmodel import Session, select

from app.db import get_session
from app.errors import ConflictError, NotFoundError, ValidationFailedError
from app.models import Hero
from app.schemas import HeroCreate

router = APIRouter(prefix="/heroes", tags=["heroes"])


@router.get("/{hero_id}", response_model=Hero)
def get_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero:
    hero = session.get(Hero, hero_id)
    if hero is None:
        # 用自訂例外取代 raise HTTPException(...)
        raise NotFoundError(
            message=f"找不到 ID 為 {hero_id} 的英雄",
            details={"hero_id": hero_id},
        )
    return hero


@router.post("/", response_model=Hero, status_code=201)
def create_hero(
    payload: HeroCreate, session: Session = Depends(get_session)
) -> Hero:
    # 業務邏輯驗證:英雄名稱不可為空(雖然 Pydantic 已檢查型別,這裡再加一層語意)
    if not payload.name.strip():
        raise ValidationFailedError(
            message="英雄名稱不可為空白",
            details={"field": "name"},
        )

    # 檢查唯一性:避免同名的英雄
    existing = session.exec(
        select(Hero).where(Hero.name == payload.name)
    ).first()
    if existing is not None:
        raise ConflictError(
            message=f"英雄名稱「{payload.name}」已被使用",
            details={"name": payload.name},
        )

    hero = Hero(name=payload.name, secret_name=payload.secret_name)
    session.add(hero)
    session.commit()
    session.refresh(hero)
    return hero

這個範例展示三種典型錯誤:「找不到」用 NotFoundError、「業務驗證」用 ValidationFailedError、「唯一鍵衝突」用 ConflictError。每個例外都帶了 message 與 details,前者給使用者、後者給前端或開發者。所有錯誤會自動被前面的全域處理器包成統一 JSON 結構,端點程式碼本身不需要處理 try/except。

驗證:用 httpx 測錯誤回應

啟動服務(命令列指令):

uvicorn app.main:app --reload --port 8000

用 httpx 測三種錯誤情境:

# test_errors.py
import httpx

BASE = "http://127.0.0.1:8000/api"

with httpx.Client(base_url=BASE, timeout=5.0) as client:
    # 情境 1:找不到英雄(404)
    r1 = client.get("/heroes/9999")
    print(r1.status_code, r1.json())
    # 輸出(範例):
    # 404 {'error': {'code': 'NOT_FOUND', 'message': '找不到 ID 為 9999 的英雄', ...}}

    # 情境 2:建立時缺欄位(422,自動被 Pydantic 攔下)
    r2 = client.post("/heroes/", json={"name": ""})
    print(r2.status_code, r2.json())
    # 輸出(範例):422 {'error': {'code': 'INVALID_INPUT', ...}}

    # 情境 3:建立時名稱衝突(409)
    r3 = client.post(
        "/heroes/", json={"name": "Spider-Man", "secret_name": "Peter"}
    )
    print(r3.status_code, r3.json())
    # 輸出(範例):409 {'error': {'code': 'CONFLICT', ...}}

三種情境的回應都帶 error 物件、code 與 message,結構完全一致。前端可以根據 code 顯示對應的錯誤訊息(例如「找不到資料」對應 toast 提示、「衝突」對應表單錯誤),而不必解析 HTTP 狀態碼與訊息字串。

未預期例外:500 的處理

最關鍵的設計是「未預期例外」的處理。寫一支故意會爆炸的端點測試:

# app/routers/heroes.py(新增)
@router.get("/_boom")
def boom() -> dict:
    # 故意觸發例外
    raise RuntimeError("這是預期外的錯誤")

用 httpx 打:

# test_500.py
import httpx

with httpx.Client(base_url="http://127.0.0.1:8000/api", timeout=5.0) as client:
    r = client.get("/heroes/_boom")
    print(r.status_code, r.json())
    # 輸出(範例):
    # 500 {'error': {'code': 'INTERNAL_ERROR', 'message': '伺服器內部錯誤,請稍後再試', ...}}

回應的訊息是「伺服器內部錯誤,請稍後再試」,沒有任何 traceback 或內部資訊。但伺服器端的 log 裡,logger.exception 會把完整 traceback 寫下來,包含是哪支函式、哪一行出錯。這個分離是資安設計的核心:對外只給必要訊息,對內給完整細節。

常見錯誤與踩雷

第一個常見的踩雷是「忘了註冊處理器」。寫完自訂例外之後,沒有呼叫 register_exception_handlers(app),結果端點 raise NotFoundError(...) 時,FastAPI 仍然用預設的 HTTPException 處理,回應結構還是 {"detail": "..."} 而不是 {"error": {...}}。檢查方式是 app.exception_handlers 字典裡有沒有你註冊的類別。

第二個是「HTTPException 在子路由裡 raise 卻被全域攔截成另一個」。FastAPI 的 add_exception_handler 是用「類別匹配」處理多型的:如果子類別先註冊,應該讓子類別的處理器贏。但實務上很多人忘了 StarletteHTTPException 是基底類別,把 HTTPException 視為「所有例外都會走它」的保險,結果自訂例外反而沒被接到。建議:自訂例外都繼承 AppError,不要直接繼承 HTTPException,並在 main.py 註冊處理器時把它們都列出來。

第三個是「log 寫太多或太少」。logger.exception 寫的是 traceback 詳細內容,適合未預期例外;但如果是 4xx(使用者錯誤),其實只要 logger.info 就好,否則 log 會被一大堆 404 淹沒,找真正的 5xx 反而困難。實務上把 4xx 視為「正常使用」、5xx 視為「需要處理的問題」是常見策略。

第四個是「request_id 沒串起來」。我們今天用 middleware 為每個請求產生 ID,但有些團隊會希望把 trace id 從上游(API gateway、load balancer)傳下來,這時應該讀 header(例如 X-Request-ID 或 W3C Trace Context),而不是自己產生。沒串起來時,跨服務除錯會非常痛苦。

效能與實務提醒

錯誤處理對效能的影響主要在兩個地方。第一,「錯誤回應的細節」:details 欄位若塞了完整 Pydantic 錯誤物件(包含 loc、msg、type、input 等),在大量驗證失敗時 body 可能變大。實務上可以視需要「過濾敏感欄位」(例如 input 可能含有使用者提交的密碼)。第二,「log 的 IO」:logger.exception 會把 traceback 序列化並寫到 log 檔,I/O 是同步的。在高 QPS 環境,建議用非同步 log handler(python-logging-async 之類的套件)或送到集中式 log 服務(ELK、Loki)。

另外,HTTP 狀態碼有一個常被忽略的細節:「422 與 400 的差別」。RFC 4918 把 422 Unprocessable Entity 定義為「伺服器理解請求內容的語法,但無法處理」(例如欄位語意錯誤);400 Bad Request 是「伺服器無法理解請求」(例如 JSON 壞掉)。FastAPI 預設用 422 表示 Pydantic 驗證失敗,這是符合規範的。我們今天設計的 ValidationFailedError 也用 422。但「找不到資源」要回 404 而不是 422,這是初學者容易搞混的地方。常見的對照:422 表示「我看了你寫的東西,邏輯上不對」;404 表示「我要找的東西不在這裡」;403 表示「東西在,但你不能碰」;409 表示「東西在,但目前狀態不允許你這個動作」。這四種狀態碼混用的後果是監控報表失準、除錯時找不到正確的入口,因此每一支端點都要先想清楚「這個錯誤對應到哪個 HTTP 狀態碼」。

最後是「錯誤代號的命名風格」。我們用全大寫底線(HERO_NOT_FOUND),這是業界常見做法(例如 AWS、Google Cloud API 的錯誤代號)。把資源名稱放進去(HERO_NOT_FOUND、TEAM_NOT_FOUND)能讓前端快速定位是哪個資源出問題。如果團隊較大,建議把錯誤代號集中在一個 enum 或文件,方便查表與翻譯。

小結

今天我們把 FastAPI 的錯誤處理從「各自例外」升級成「統一結構」。自訂例外類別把 HTTP 狀態碼與錯誤代號綁在一起,全域處理器把它們包成 {error: {code, message, details, request_id}} 的固定 JSON 結構,並透過 middleware 為每個請求帶上 request_id,方便對應 log。我們也處理了 Pydantic 結構驗證錯誤、SQLAlchemy 唯一鍵衝突、未預期例外三種典型情境。這套架構會在接下來的安全主題(Web Day 11–13)持續沿用,到時候 UnauthorizedError、ForbiddenError 都會接上同一個處理器,整個系統的錯誤回應會保持一致。

結語

今天把 API 回應設計的地基打好了,明天我們要進入安全主題的第一篇:密碼雜湊與註冊流程。你會學到為什麼不能直接存明文密碼、bcrypt 與 argon2 的取捨、怎麼用 passlib 或 argon2-cffi 把密碼正確地雜湊並驗證,並設計一支「註冊端點」,把今天設計的 ConflictError 拿來處理「帳號已被註冊」的情境。整套架構會從今天延伸到後續的認證與權限(Web Day 12–13)。

延伸資源

  • FastAPI 官方文件:處理錯誤(0.116,2025):https://fastapi.tiangolo.com/tutorial/handling-errors/
  • FastAPI 官方文件:自訂 Response(0.116,2025):https://fastapi.tiangolo.com/advanced/custom-response/
  • Pydantic v2 錯誤結構(2.11,2025):https://docs.pydantic.dev/latest/errors/
  • HTTP 狀態碼標準(IETF RFC 9110,2025):https://www.rfc-editor.org/rfc/rfc9110.html

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...