跳到主要內容

Web Day 13 OAuth2 與角色授權

Web Day 13 OAuth2 與角色授權

執行需求:CPU 可跑。昨天我們用 JWT 做出「登入 → 發 token → 驗 token → 取得目前使用者」這套基本流程。今天要把昨天的基礎延伸成「角色授權」:讓 API 能區分「一般使用者」與「管理者」,並在端點上宣告「只有管理者能呼叫」。我們會用 FastAPI 的 Security 與 SecurityScopes 實作 OAuth2 scope 概念,並設計 require_role 與 require_admin 兩支可重複使用的相依性,後續的「預約管理系統」專案篇(Web Day 35+)會直接沿用這套設計。

引言

昨天的 JWT 認證解決了「你是誰」的問題,但還沒處理「你能做什麼」。在真實系統裡,使用者通常分幾種角色:訪客(未登入)、一般使用者(可以建立訂單、查自己的資料)、管理者(可以查所有訂單、刪除違規內容)。如果每個端點都自己寫「檢查使用者的角色」的程式碼,會出現幾個問題:第一,邏輯散落在各處,難以統一審查;第二,新人很容易在某個端點忘了檢查角色,導致權限漏洞;第三,OAuth2 規範定義的 scope 概念無法落實。

「授權」(authorization)和「認證」(authentication)是兩件事。認證是「你是誰」,授權是「你能做什麼」。昨天的 JWT 處理認證,今天處理授權。常見的授權模型有 RBAC(Role-Based Access Control)與 ABAC(Attribute-Based Access Control),前者以角色為中心、後者以屬性為中心。我們今天用 RBAC,因為它最直覺、SQL 表格也簡單,適用於大多數後端系統。RBAC 的優點是角色數量通常很少(管理員、一般使用者、編輯者等),新增角色只要加 enum 值;缺點是當業務複雜度提升,「角色」與「權限」變得多對多時,維護成本會迅速上升。ABAC 則是用條件表達式(例如「使用者只能改自己的資料」「管理者只能改同部門的資料」),靈活度高但實作複雜,適合企業級應用。多數新專案從 RBAC 開始,等真的碰到複雜情境再升級,是務實的選擇。

今天的目標有三個:第一,把昨天的 User 模型擴充成支援角色(用 enum);第二,設計 require_role(...) 與 require_admin 兩個可重複使用的相依性;第三,把 FastAPI 的 Security 與 SecurityScopes 整合進去,讓 Swagger UI 能正確顯示「這個端點需要 admin scope」。整篇文章的程式碼會在後續的預約管理系統中直接套用,特別是 Web Day 36 會再次用到今天的 require_admin 設計。

角色模型:把「身份」變成「權限」

角色(role)的本質是「一群權限的代稱」。我們用 Python 的 Enum 來定義,並把這個欄位加進昨天的 User 模型:

# app/models.py
from datetime import datetime
from enum import Enum
from typing import Optional

from sqlmodel import Field, SQLModel


class UserRole(str, Enum):
    # 使用者角色:admin 可以管理所有資料;user 只能存取自己的
    ADMIN = "admin"
    USER = "user"


class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    email: str = Field(index=True, unique=True)
    password_hash: str
    is_active: bool = Field(default=True)
    # 新增角色欄位,預設為一般使用者
    role: UserRole = Field(default=UserRole.USER)
    created_at: datetime = Field(default_factory=datetime.utcnow)

用 str, Enum 的雙重繼承讓 SQLModel 與 Pydantic 都能正確序列化為字串。預設值是 UserRole.USER,這樣昨天寫的註冊端點不需要任何修改,新註冊的使用者自動成為一般使用者。資料庫存的是字串("admin" 或 "user"),與 enum 的 value 一致,避免序列化時的困擾。UserRole(str, Enum) 這種雙重繼承是 SQLModel 與 Pydantic v2 推薦的做法,str 讓 enum 成員可以直接當作字串使用(例如 role == "admin" 這種比較會通過),而 Enum 則提供型別安全與自動完成。實務上若忘了 str,序列化時 enum 會變成 UserRole.ADMIN 這種帶類別名稱的字串,前端拿到就會出問題。

接下來要做 Alembic 遷移(昨天學的)。我們在 User 上加了 role 欄位,需要一支遷移把它加進資料表:

alembic revision --autogenerate -m "add user role column"
alembic upgrade head

Alembic 會偵測到 role 欄位是新的,產生一支 add_column 動作的遷移檔。執行 upgrade head 後,所有既有使用者都會被自動填上預設值 "user",不會破壞資料。

設計 require_role 相依性

FastAPI 的相依性注入(Web Day 5)允許我們把「檢查權限」這件事包成一支可重複使用的 dependency。先擴充昨天的 app/dependencies.py:

# app/dependencies.py(接續昨天的版本)
from typing import Annotated

from fastapi import Depends, Request, Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlmodel import Session, select

from app.db import get_session
from app.errors import ForbiddenError, UnauthorizedError
from app.models import User, UserRole
from app.tokens import decode_token

bearer_scheme = HTTPBearer(auto_error=False)


def get_current_user(
    request: Request,
    credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
    session: Session = Depends(get_session),
) -> User:
    # 從 token 取使用者(昨天實作,這裡只列出重點)
    if credentials is None or not credentials.credentials:
        raise UnauthorizedError(message="缺少認證資訊")
    try:
        payload = decode_token(credentials.credentials, expected_type="access")
    except ValueError as exc:
        raise UnauthorizedError(message=str(exc))
    user_id = int(payload["sub"])
    user = session.get(User, user_id)
    if user is None or not user.is_active:
        raise UnauthorizedError(message="帳號不存在或已停用")
    return user


CurrentUser = Annotated[User, Depends(get_current_user)]


def require_role(*allowed_roles: UserRole):
    # 工廠函式:產生一支檢查角色的 dependency
    # 用法:user: Annotated[User, Depends(require_role(UserRole.ADMIN))]
    def _checker(user: CurrentUser) -> User:
        if user.role not in allowed_roles:
            raise ForbiddenError(
                message="權限不足",
                details={
                    "required": [r.value for r in allowed_roles],
                    "current": user.role.value,
                },
            )
        return user

    return _checker


# 常用的角色檢查工廠
require_admin = require_role(UserRole.ADMIN)
require_user = require_role(UserRole.USER, UserRole.ADMIN)

require_role 是一個工廠函式(factory function):呼叫它傳入「允許的角色清單」,它回傳一支相依性。這個設計的好處是「一個工廠、多種組合」:要「只有 admin」傳 UserRole.ADMIN、要「user 或 admin」傳兩個值,未來加新角色(例如 EDITOR)只要改 enum,工廠不用動。ForbiddenError 是 Web Day 10 定義的 403 例外,這裡接上昨天的統一錯誤結構。details 帶上「需要什麼角色」與「目前是什麼角色」,方便前端顯示對應訊息。

OAuth2 scope 與 SecurityScopes

FastAPI 對 OAuth2 的 scope 概念有原生支援:透過 Security 與 SecurityScopes,可以在 OpenAPI(Swagger)文件裡正確顯示「這個端點需要 admin scope」。這對前端串接與第三方整合特別重要:他們看文件就知道這個 API 要帶什麼權限。我們把昨天的設計升級成 OAuth2 相容版本:

# app/dependencies.py(OAuth2 升級版)
from typing import Annotated

from fastapi import Depends, Security
from fastapi.security import (
    HTTPAuthorizationCredentials,
    HTTPBearer,
    SecurityScopes,
)
from sqlmodel import Session

from app.db import get_session
from app.errors import ForbiddenError, UnauthorizedError
from app.models import User, UserRole
from app.tokens import decode_token

# scope 與角色對應:scope 是 OAuth2 標準術語,角色是內部模型
# 把 admin 對應到 "admin" scope,user 對應到 "user" scope
ROLE_TO_SCOPE = {
    UserRole.ADMIN: "admin",
    UserRole.USER: "user",
}

bearer_scheme = HTTPBearer(
    auto_error=False,
    scopes={
        "admin": "管理者權限",
        "user": "一般使用者權限",
    },
)


def get_current_user(
    security_scopes: SecurityScopes,
    credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
    session: Session = Depends(get_session),
) -> User:
    # 驗證 token 並檢查 scope
    if credentials is None or not credentials.credentials:
        raise UnauthorizedError(message="缺少認證資訊")

    try:
        payload = decode_token(credentials.credentials, expected_type="access")
    except ValueError as exc:
        raise UnauthorizedError(message=str(exc))

    user_id = int(payload["sub"])
    user = session.get(User, user_id)
    if user is None or not user.is_active:
        raise UnauthorizedError(message="帳號不存在或已停用")

    # 檢查 scope:使用者角色必須涵蓋端點要求的所有 scope
    if security_scopes.scopes:
        user_scope = ROLE_TO_SCOPE.get(user.role)
        if user_scope is None or user_scope not in security_scopes.scopes:
            raise ForbiddenError(
                message="權限不足",
                details={
                    "required_scopes": security_scopes.scopes,
                    "current_scope": user_scope,
                },
            )

    return user


CurrentUser = Annotated[User, Security(get_current_user)]


def require_role(*allowed_roles: UserRole):
    # 同昨天的設計
    def _checker(user: CurrentUser) -> User:
        if user.role not in allowed_roles:
            raise ForbiddenError(
                message="權限不足",
                details={
                    "required": [r.value for r in allowed_roles],
                    "current": user.role.value,
                },
            )
        return user

    return _checker


require_admin = require_role(UserRole.ADMIN)
require_user = require_role(UserRole.USER, UserRole.ADMIN)

這段有幾個關鍵升級。第一,HTTPBearer(scopes=...) 把 scope 宣告在 security scheme 裡,這樣 OpenAPI 文件會顯示「這個 API 用 Bearer token,且支援 admin/user scope」。第二,get_current_user 多了 security_scopes: SecurityScopes 參數,FastAPI 會把端點要求的 scope 清單自動傳進來,我們用它和使用者的角色對比。第三,CurrentUser 用 Security(...) 而非 Depends(...),讓 OpenAPI 文件知道這是「需要授權」的端點。

在端點上宣告需要的權限

現在來看一支需要管理者權限的端點。我們擴充昨天的英雄路由,加入「列出所有英雄(管理者專用)」與「刪除英雄(管理者專用)」:

# app/routers/heroes.py(新增管理者端點)
from typing import Annotated

from fastapi import APIRouter, Depends, Security, status
from sqlmodel import Session, select

from app.db import get_session
from app.dependencies import CurrentUser, require_admin, require_user
from app.errors import ForbiddenError, NotFoundError
from app.models import Hero, User
from app.schemas import HeroCreate

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


@router.get("/")
def list_heroes(
    user: Annotated[User, Security(require_user)],
    session: Session = Depends(get_session),
) -> list[Hero]:
    # 任何登入的使用者都可以列出英雄
    heroes = session.exec(select(Hero)).all()
    return heroes


@router.delete("/{hero_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_hero(
    hero_id: int,
    user: Annotated[User, Security(require_admin)],
    session: Session = Depends(get_session),
) -> None:
    # 只有管理者能刪除英雄
    hero = session.get(Hero, hero_id)
    if hero is None:
        raise NotFoundError(
            message=f"找不到 ID 為 {hero_id} 的英雄",
            details={"hero_id": hero_id},
        )
    session.delete(hero)
    session.commit()

這裡用 Security(require_admin) 把權限需求掛在端點上。FastAPI 會自動做兩件事:第一,OpenAPI 文件會顯示「這個端點需要 admin scope」;第二,當 request 進來時,get_current_user 會被呼叫,security_scopes.scopes 會包含 ["admin"],我們用它和使用者角色對比,不符就回 403。整個授權檢查完全自動,端點程式碼只關心業務邏輯。

新增角色的管理工具

實務上需要一支「管理者才能呼叫」的端點來變更其他使用者的角色。我們在 app/routers/admin.py 裡集中放管理者工具:

# app/schemas.py(新增)
from pydantic import BaseModel

from app.models import UserRole


class UpdateUserRoleRequest(BaseModel):
    role: UserRole


class UserSummary(BaseModel):
    id: int
    email: str
    role: UserRole
    is_active: bool

管理者路由:

# app/routers/admin.py
from typing import Annotated

from fastapi import APIRouter, Depends, Security
from sqlmodel import Session, select

from app.db import get_session
from app.dependencies import require_admin
from app.errors import NotFoundError
from app.models import User
from app.schemas import UpdateUserRoleRequest, UserSummary

router = APIRouter(
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(require_admin)],
)


@router.get("/users", response_model=list[UserSummary])
def list_users(session: Session = Depends(get_session)) -> list[User]:
    # 列出所有使用者(僅管理者)
    users = session.exec(select(User)).all()
    return users


@router.patch("/users/{user_id}/role", response_model=UserSummary)
def update_user_role(
    user_id: int,
    payload: UpdateUserRoleRequest,
    session: Session = Depends(get_session),
) -> User:
    # 變更使用者角色(僅管理者)
    user = session.get(User, user_id)
    if user is None:
        raise NotFoundError(message="使用者不存在")
    user.role = payload.role
    session.add(user)
    session.commit()
    session.refresh(user)
    return user

注意 router = APIRouter(..., dependencies=[Depends(require_admin)]):把 require_admin 掛在整個 router 上,這個 router 底下的所有端點都自動需要管理者權限,不必每支端點都加一次。這種「群組權限」設計對大型 API 很方便:可以把所有管理端點集中在 admin router,一次設定全部生效。

建立第一個管理者的工具腳本

因為註冊端點預設給一般使用者角色,第一個管理者要怎麼產生?我們寫一支獨立腳本,繞過註冊流程直接建立管理者帳號:

# scripts/create_admin.py
# 用法:python -m scripts.create_admin alice@example.com MyP@ssw0rd-2025
import sys
from datetime import datetime

from sqlmodel import Session, select

from app.db import engine
from app.models import User, UserRole
from app.security import hash_password


def main() -> None:
    if len(sys.argv) != 3:
        print("用法:python -m scripts.create_admin <email> <password>")
        sys.exit(1)

    email, password = sys.argv[1], sys.argv[2]
    with Session(engine) as session:
        existing = session.exec(
            select(User).where(User.email == email)
        ).first()
        if existing is not None:
            print(f"使用者 {email} 已存在,更新為 admin")
            existing.role = UserRole.ADMIN
            session.add(existing)
        else:
            user = User(
                email=email,
                password_hash=hash_password(password),
                role=UserRole.ADMIN,
                created_at=datetime.utcnow(),
            )
            session.add(user)
        session.commit()
        print(f"完成:{email} 現在是 admin")


if __name__ == "__main__":
    main()

這支腳本透過命令列參數建立或升級管理者帳號,正式環境通常會把這個流程改成「由現有管理者透過後台變更」或「透過一次性邀請碼」。但開發階段用 CLI 腳本最快。注意 hash_password 是昨天的工具,雜湊與角色設定都在同一支腳本裡。

驗證完整流程

啟動服務(命令列):

# 建立管理者
python -m scripts.create_admin admin@example.com AdminP@ss-2025
# 建立一般使用者
python -m scripts.create_admin alice@example.com AliceP@ss-2025
# 啟動服務
uvicorn app.main:app --reload --port 8000

用 httpx 驗證三種情境:

# test_roles.py
import httpx

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


def login(client: httpx.Client, email: str, password: str) -> str:
    r = client.post(
        "/auth/login", json={"email": email, "password": password}
    )
    r.raise_for_status()
    return r.json()["access_token"]


with httpx.Client(base_url=BASE, timeout=10.0) as client:
    # 兩種使用者登入
    admin_token = login(client, "admin@example.com", "AdminP@ss-2025")
    user_token = login(client, "alice@example.com", "AliceP@ss-2025")

    admin_headers = {"Authorization": f"Bearer {admin_token}"}
    user_headers = {"Authorization": f"Bearer {user_token}"}

    # 情境 1:一般使用者列出英雄(200,允許)
    r = client.get("/heroes/", headers=user_headers)
    print(f"user GET /heroes/:{r.status_code}")

    # 情境 2:一般使用者刪英雄(403,權限不足)
    r = client.delete("/heroes/1", headers=user_headers)
    print(f"user DELETE /heroes/1:{r.status_code}", r.json())
    # 輸出(範例):
    # 403 {'error': {'code': 'FORBIDDEN', ...}}

    # 情境 3:管理者刪英雄(204,成功)
    r = client.delete("/heroes/1", headers=admin_headers)
    print(f"admin DELETE /heroes/1:{r.status_code}")
    # 輸出(範例):204

    # 情境 4:未登入存取管理者端點(401)
    r = client.get("/admin/users")
    print(f"未登入 GET /admin/users:{r.status_code}", r.json())
    # 輸出(範例):
    # 401 {'error': {'code': 'UNAUTHORIZED', ...}}

    # 情境 5:使用者存取管理者端點(403)
    r = client.get("/admin/users", headers=user_headers)
    print(f"user GET /admin/users:{r.status_code}", r.json())
    # 輸出(範例):
    # 403 {'error': {'code': 'FORBIDDEN', ...}}

五個情境完整覆蓋認證 + 授權的所有路徑。每一個錯誤都走 Web Day 10 設計的 UnauthorizedError 或 ForbiddenError,回應結構一致。前端可以根據 code 與 status_code 決定行為:401 走「重新登入」流程,403 顯示「權限不足」訊息。如果你想看更細的錯誤結構,可以把 details 印出來:它會帶上「需要 admin scope」與「目前是 user scope」這類資訊,前端可以針對性地顯示「請聯絡管理員開通權限」這類提示文字。

另一個值得驗證的是 Swagger UI:開啟 http://127.0.0.1:8000/docs,點開 DELETE /heroes/{hero_id} 會看到右上角有個小鎖頭圖示,標示「需要 admin scope」。這就是 OAuth2 SecurityScopes 的功用:讓文件本身就說明權限需求。同時,Swagger UI 還提供「Authorize」按鈕,讓你可以貼上一個 admin token,整個 API 文件就會自動帶這個 token 測試所有端點。這個對前端串接、後端測試、第三方整合都是極大的便利,是 FastAPI 文件系統在業界受歡迎的主因之一。

常見錯誤與踩雷

第一個常見的踩雷是「把角色檢查寫在每個端點裡」。新手常這樣寫:

# 錯誤示範
@router.delete("/heroes/{hero_id}")
def delete_hero(hero_id: int, user: CurrentUser, session: Session = Depends(get_session)):
    if user.role != "admin":
        raise HTTPException(403, "需要管理員權限")
    # ... 業務邏輯

這有兩個問題:第一,邏輯散落在各處,新人容易在某個端點忘了檢查;第二,OpenAPI 文件不會顯示這個端點需要權限。我們用 require_role 工廠與 Security 註冊解決這個問題。

第二個是「忘了升級 Alembic 遷移」。當我們在 User 加了 role 欄位後,必須跑 alembic revision --autogenerate 並 alembic upgrade head,否則正式環境的資料表沒有這個欄位,註冊或登入時就會出錯。Web Day 9 建立的遷移流程正是為這種情境設計的。

第三個是「scope 名稱與角色名稱混淆」。OAuth2 的 scope 是「資源的權限單位」(例如 read:heroes、write:heroes),角色是「使用者的身份」(admin、user)。兩者不一定一對一:一個 admin 可能有多個 scope,一個 scope 可能被多個角色擁有。今天的範例為了簡化把兩者對應起來(admin 角色 → admin scope),但實務上要分開:scope 表達「能做什麼事」,角色表達「是什麼人」,中間用一個 mapping 串起來。

第四個是「忘記 Security 與 Depends 的差別」。Depends 只是單純的相依性注入;Security 除了注入之外,還會把這個 dependency 標記為「需要授權」,反映在 OpenAPI 文件上。要讓 Swagger UI 顯示鎖頭圖示,必須用 Security 而非 Depends。

效能與實務提醒

角色檢查的效能成本極低(只是 enum 成員的比較),但「每次請求都查一次 User 表」是常見的瓶頸。如果你的 API 高頻呼叫且對 latency 敏感,可以考慮兩個方向:第一,把使用者物件(含角色)放進 Redis 快取,5 秒過期;第二,把 role 放進 JWT 的 payload,這樣驗 token 時順便拿到角色,不必額外查資料庫。第二個做法的權衡是「角色變更無法即時生效」,因為已經發出去的 token 還帶著舊角色。實務上很少需要即時變更角色(管理者降級一般使用者通常是「等使用者重新登入」這個情境),所以這個權衡在大多數小型到中型的系統是可接受的,這個範圍涵蓋大多數的實務情境。另外,role 放進 payload 還有一個好處是「跨服務一致」:如果未來你有多個微服務各自驗證 JWT,把角色放在 token 裡就不必每個服務各自去查 User 表,統一的權限來源就在 token 裡。當然,這個做法的前提是 access token 有效期要短(昨天的 30 分鐘設計就符合),否則角色變更要等 token 過期才生效,會讓管理操作變得遲鈍。

另一個議題是「角色數量」。我們今天只有 admin 與 user 兩種,簡單明瞭。但真實系統可能有十幾種角色(editor、viewer、billing、support 等等),這時 RBAC 會變得難以維護,因為角色與權限的對應變成多對多。實務上到了這個規模,建議改用 ABAC 或引入權限管理系統(例如 Casbin、Oso)。我們今天先求正確理解 RBAC,未來若角色數量膨脹再升級。

最後提醒:「SecurityScopes 的 scope 宣告要與 HTTPBearer(scopes=...) 一致」。如果在 HTTPBearer 沒宣告某個 scope,但端點用 Security(... scopes=["xxx"]) 要求它,OpenAPI 文件會出現奇怪的顯示。維護這份對應是必要的:每次新增 scope 要同步更新 HTTPBearer(scopes=...) 與 ROLE_TO_SCOPE。

小結

今天把昨天的 JWT 認證升級成完整的角色授權系統。我們用 UserRole enum 與 role 欄位把角色帶進 User 模型,並用 Alembic 遷移更新資料庫;require_role 工廠與 require_admin 把「檢查權限」變成可重複使用的相依性;SecurityScopes 與 Security 則把 OAuth2 scope 概念整合進 FastAPI,讓 Swagger UI 正確顯示權限需求。所有錯誤(401 未登入、403 權限不足)都走 Web Day 10 設計的統一 JSON 結構,前端可以無歧義地處理。這套架構會在後續的預約管理系統(Web Day 35+)直接套用,特別是「管理者後台」與「使用者只能改自己的預約」這兩個情境。貫穿整個安全主題,我們已經完成四個關鍵設計:密碼以 argon2id 單向雜湊(Web Day 11)、JWT 認證與無狀態 token(Web Day 12)、OAuth2 角色授權(Web Day 13)、明天會做的輸入防護(CORS、SQL 注入、XSS)。這四個支柱放在一起,就構成了一個對中型 API 來說已經足夠的資安基礎;更高階的威脅模型(rate limiting、audit log、機密偵測)會在品質與上線篇章陸續補上。

結語

今天完成了 OAuth2 與角色授權,安全主題的四大支柱(密碼雜湊、JWT 認證、OAuth2 角色、輸入防護)已經做了一半。明天進入「檔案上傳與靜態檔案」:你會學到 FastAPI 的 UploadFile 與 File 怎麼處理使用者上傳的頭像、怎麼限制檔案大小與 MIME 型別、怎麼用 StaticFiles 把上傳後的圖片透過 URL 對外提供,以及資安上該注意的「不存可執行檔、隨機檔名、過濾 magic bytes」等實務守則。這是後續「預約管理系統」中客戶頭像與預約附件功能的基礎。

延伸資源

  • FastAPI 官方文件:安全性與 OAuth2(0.116,2025):https://fastapi.tiangolo.com/tutorial/security/
  • FastAPI 官方文件:SecurityScopes(0.116,2025):https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/
  • OAuth 2.0 RFC 6749(2025):https://datatracker.ietf.org/doc/html/rfc6749
  • OWASP 存取控制 cheat sheet(2025):https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.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 中,資料型別決定我們可以對變數進行哪些操作...

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 建構深度學習模型。 開發者與研究人員 :想更深入了...

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 等工具能處理和分析龐...