# 

# 06-2. yield 依賴

&gt; ⏱️ **閱讀時間：** 15 分鐘
&gt; 🎯 **難度：** ⭐⭐⭐ (進階)

---

## 🤔 一句話解釋

**yield 依賴讓你在請求處理前後執行程式碼，常用於資源的取得和釋放，如資料庫連線。**

---

## 🔄 普通依賴 vs yield 依賴

```python
# 普通依賴：只在請求前執行
def get_db():
    db = Database()
    return db  # 沒有清理！

# yield 依賴：請求前後都執行
def get_db():
    db = Database()
    try:
        yield db  # 請求處理前
    finally:
        db.close()  # 請求處理後
```

```
普通依賴：
請求 ─▶ get_db() ─▶ 端點函數 ─▶ 回應
        │
        └── 建立 DB（沒有關閉）

yield 依賴：
請求 ─▶ get_db() ─▶ 端點函數 ─▶ get_db() ─▶ 回應
        │                        │
        └── yield db             └── finally: db.close()
           （請求前）                （請求後）
```

---

## 🎯 使用場景

| 場景 | 說明 |
|------|------|
| **資料庫連線** | 取得/釋放連線 |
| **檔案處理** | 開啟/關閉檔案 |
| **交易管理** | 開始/提交/回滾 |
| **鎖定機制** | 取得/釋放鎖 |
| **臨時資源** | 建立/清理臨時檔案 |

---

## 📦 基本用法

### 資料庫 Session

```python
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session

app = FastAPI()

# 建立資料庫引擎
engine = create_engine(&#34;sqlite:///./test.db&#34;)
SessionLocal = sessionmaker(bind=engine)

def get_db():
    &#34;&#34;&#34;資料庫 Session 依賴&#34;&#34;&#34;
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get(&#34;/users&#34;)
def get_users(db: Session = Depends(get_db)):
    return db.query(User).all()
```

### 非同步版本

```python
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

app = FastAPI()

engine = create_async_engine(&#34;postgresql&#43;asyncpg://...&#34;)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession)

async def get_db():
    &#34;&#34;&#34;非同步資料庫 Session&#34;&#34;&#34;
    async with AsyncSessionLocal() as session:
        yield session

@app.get(&#34;/users&#34;)
async def get_users(db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(User))
    return result.scalars().all()
```

---

## 🔧 進階用法

### 交易管理

```python
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session

app = FastAPI()

def get_db_with_transaction():
    &#34;&#34;&#34;帶交易的資料庫 Session&#34;&#34;&#34;
    db = SessionLocal()
    try:
        yield db
        db.commit()  # 成功則提交
    except Exception:
        db.rollback()  # 失敗則回滾
        raise
    finally:
        db.close()

@app.post(&#34;/transfer&#34;)
def transfer_money(
    from_id: int,
    to_id: int,
    amount: float,
    db: Session = Depends(get_db_with_transaction)
):
    # 如果這裡拋出異常，交易會自動回滾
    from_account = db.query(Account).get(from_id)
    to_account = db.query(Account).get(to_id)

    from_account.balance -= amount
    to_account.balance &#43;= amount

    return {&#34;status&#34;: &#34;success&#34;}
```

### 檔案處理

```python
from fastapi import FastAPI, Depends
import tempfile
import os

app = FastAPI()

def get_temp_file():
    &#34;&#34;&#34;臨時檔案依賴&#34;&#34;&#34;
    fd, path = tempfile.mkstemp()
    try:
        yield path
    finally:
        os.close(fd)
        os.unlink(path)  # 刪除臨時檔案

@app.post(&#34;/process&#34;)
async def process_data(temp_path: str = Depends(get_temp_file)):
    # 使用臨時檔案
    with open(temp_path, &#39;w&#39;) as f:
        f.write(&#34;處理中的資料...&#34;)

    # 請求結束後，臨時檔案會被自動刪除
    return {&#34;temp_file&#34;: temp_path}
```

### 分散式鎖

```python
from fastapi import FastAPI, Depends, HTTPException
import redis
import uuid

app = FastAPI()
redis_client = redis.Redis()

def acquire_lock(resource: str):
    &#34;&#34;&#34;取得分散式鎖&#34;&#34;&#34;
    lock_key = f&#34;lock:{resource}&#34;
    lock_value = str(uuid.uuid4())

    # 嘗試取得鎖
    acquired = redis_client.set(
        lock_key,
        lock_value,
        nx=True,
        ex=30  # 30 秒過期
    )

    if not acquired:
        raise HTTPException(status_code=423, detail=&#34;Resource locked&#34;)

    try:
        yield lock_value
    finally:
        # 使用 Lua 腳本安全釋放鎖
        script = &#34;&#34;&#34;
        if redis.call(&#34;get&#34;, KEYS[1]) == ARGV[1] then
            return redis.call(&#34;del&#34;, KEYS[1])
        else
            return 0
        end
        &#34;&#34;&#34;
        redis_client.eval(script, 1, lock_key, lock_value)

@app.post(&#34;/inventory/{item_id}/update&#34;)
def update_inventory(
    item_id: int,
    quantity: int,
    lock: str = Depends(lambda: acquire_lock(f&#34;inventory:{item_id}&#34;))
):
    # 更新庫存（有鎖保護）
    return {&#34;item_id&#34;: item_id, &#34;quantity&#34;: quantity}
```

---

## 📊 多個 yield 依賴

### 依賴順序

```python
from fastapi import FastAPI, Depends

app = FastAPI()

def dep_a():
    print(&#34;A: 開始&#34;)
    yield &#34;A&#34;
    print(&#34;A: 結束&#34;)

def dep_b():
    print(&#34;B: 開始&#34;)
    yield &#34;B&#34;
    print(&#34;B: 結束&#34;)

def dep_c(a: str = Depends(dep_a), b: str = Depends(dep_b)):
    print(&#34;C: 開始&#34;)
    yield f&#34;{a}&#43;{b}&#34;
    print(&#34;C: 結束&#34;)

@app.get(&#34;/test&#34;)
def test_endpoint(c: str = Depends(dep_c)):
    print(&#34;端點執行&#34;)
    return {&#34;result&#34;: c}

# 輸出順序：
# A: 開始
# B: 開始
# C: 開始
# 端點執行
# C: 結束
# B: 結束
# A: 結束
```

### 執行流程圖

```
請求進入
    │
    ▼
┌─────────────────┐
│   dep_a 開始    │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│   dep_b 開始    │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│   dep_c 開始    │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│   端點執行      │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│   dep_c 結束    │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│   dep_b 結束    │
└─────────────────┘
    │
    ▼
┌─────────────────┐
│   dep_a 結束    │
└─────────────────┘
    │
    ▼
回應發送
```

---

## ⚠️ 異常處理

### 在 yield 後捕獲異常

```python
from fastapi import FastAPI, Depends, HTTPException

app = FastAPI()

def get_db():
    db = SessionLocal()
    try:
        yield db
    except Exception as e:
        # 可以在這裡處理異常
        db.rollback()
        raise  # 重新拋出異常
    finally:
        db.close()
```

### HTTPException 的處理

```python
from fastapi import FastAPI, Depends, HTTPException

app = FastAPI()

def resource_dependency():
    resource = acquire_resource()
    try:
        yield resource
    except Exception as e:
        # 注意：HTTPException 也會在這裡被捕獲
        release_resource(resource)
        raise

@app.get(&#34;/items/{item_id}&#34;)
def get_item(
    item_id: int,
    resource = Depends(resource_dependency)
):
    if item_id &lt; 0:
        raise HTTPException(status_code=400, detail=&#34;Invalid ID&#34;)
    return {&#34;item_id&#34;: item_id}
```

### 確保清理執行

```python
from fastapi import FastAPI, Depends

app = FastAPI()

def safe_dependency():
    &#34;&#34;&#34;確保清理一定執行&#34;&#34;&#34;
    resource = None
    try:
        resource = acquire_resource()
        yield resource
    finally:
        # finally 區塊一定會執行
        if resource:
            release_resource(resource)
```

---

## 📝 實戰範例：完整的資料庫操作

```python
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, Session, declarative_base
from pydantic import BaseModel
from typing import List

# 資料庫設定
engine = create_engine(&#34;sqlite:///./test.db&#34;)
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()

# 模型
class UserModel(Base):
    __tablename__ = &#34;users&#34;
    id = Column(Integer, primary_key=True)
    name = Column(String(100))
    email = Column(String(100), unique=True)

Base.metadata.create_all(engine)

# Pydantic 模型
class UserCreate(BaseModel):
    name: str
    email: str

class UserResponse(BaseModel):
    id: int
    name: str
    email: str

    class Config:
        from_attributes = True

# 依賴
def get_db():
    &#34;&#34;&#34;取得資料庫 Session&#34;&#34;&#34;
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

def get_db_transactional():
    &#34;&#34;&#34;取得帶交易的資料庫 Session&#34;&#34;&#34;
    db = SessionLocal()
    try:
        yield db
        db.commit()
    except Exception:
        db.rollback()
        raise
    finally:
        db.close()

# FastAPI 應用
app = FastAPI()

@app.get(&#34;/users&#34;, response_model=List[UserResponse])
def list_users(
    skip: int = 0,
    limit: int = 100,
    db: Session = Depends(get_db)
):
    &#34;&#34;&#34;列出所有使用者&#34;&#34;&#34;
    users = db.query(UserModel).offset(skip).limit(limit).all()
    return users

@app.get(&#34;/users/{user_id}&#34;, response_model=UserResponse)
def get_user(user_id: int, db: Session = Depends(get_db)):
    &#34;&#34;&#34;取得單一使用者&#34;&#34;&#34;
    user = db.query(UserModel).filter(UserModel.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail=&#34;User not found&#34;)
    return user

@app.post(&#34;/users&#34;, response_model=UserResponse)
def create_user(
    user: UserCreate,
    db: Session = Depends(get_db_transactional)
):
    &#34;&#34;&#34;建立使用者&#34;&#34;&#34;
    # 檢查 email 是否已存在
    existing = db.query(UserModel).filter(
        UserModel.email == user.email
    ).first()

    if existing:
        raise HTTPException(status_code=400, detail=&#34;Email already exists&#34;)

    db_user = UserModel(**user.model_dump())
    db.add(db_user)
    db.flush()  # 取得 ID
    db.refresh(db_user)

    return db_user

@app.delete(&#34;/users/{user_id}&#34;)
def delete_user(
    user_id: int,
    db: Session = Depends(get_db_transactional)
):
    &#34;&#34;&#34;刪除使用者&#34;&#34;&#34;
    user = db.query(UserModel).filter(UserModel.id == user_id).first()
    if not user:
        raise HTTPException(status_code=404, detail=&#34;User not found&#34;)

    db.delete(user)
    return {&#34;message&#34;: &#34;User deleted&#34;}
```

---

## ✅ 重點總結

### yield 依賴特性

| 特性 | 說明 |
|------|------|
| **yield 前** | 請求處理前執行 |
| **yield 後** | 請求處理後執行 |
| **finally** | 一定會執行 |
| **執行順序** | 後進先出（LIFO）|

### 使用場景

| 場景 | 範例 |
|------|------|
| **資源管理** | 資料庫連線 |
| **交易** | commit/rollback |
| **鎖定** | 分散式鎖 |
| **清理** | 臨時檔案 |

### 最佳實踐

1. 使用 `try/finally` 確保清理
2. 異常處理放在 `except` 區塊
3. 重要清理邏輯放在 `finally`
4. 考慮非同步版本的效能

---

## 🎤 面試這樣答

### Q: FastAPI 的 yield 依賴有什麼用？

**答案：**

&gt; yield 依賴讓你在請求處理**前後**執行程式碼：
&gt;
&gt; ```python
&gt; def get_db():
&gt;     db = SessionLocal()
&gt;     try:
&gt;         yield db      # 請求前：提供 db
&gt;     finally:
&gt;         db.close()    # 請求後：關閉連線
&gt; ```
&gt;
&gt; **執行順序：**
&gt; 1. yield 前的程式碼
&gt; 2. 端點函數
&gt; 3. yield 後的程式碼（清理）
&gt;
&gt; **常見用途：**
&gt; - 資料庫連線管理
&gt; - 交易管理（commit/rollback）
&gt; - 資源清理

---

**上一篇：** [06-1. 依賴注入基礎](./06-1)
**下一篇：** [06-3. 依賴覆蓋](./06-3)

---

最後更新：2025-12-18


---

> 作者: luk  
> URL: https://yoru-karu-blog-lalaluk-52581ac5e0cef170a3c8922c19182ecb6f7bd604.gitlab.io/posts/tutorial/fastapi/06-2/  

