python使用泛型

403·Forbidden發表於2022-02-16

所謂的泛型, 就是將資料型別作為引數進行傳遞, 即在我們用的時候確定資料型別, 這是一種在面嚮物件語言中經常使用的特性

一般類使用

以SQLAlchemy舉例

比如: 我們統一寫個將資料儲存到資料庫的介面, 只有將資料庫連結 表物件 資料傳入即可, 返回的是表物件的例項, 為了讓IDE可以識別返回物件, 我們可以使用泛型
這裡需要用到:

  1. typingTypeVarType
    TypeVar是型別變數, 主要用於泛型型別與泛型函式定義的引數, 第一個引數是名稱, bound引數用於規定該型別為bound值的子類
    Type[C]的形式為協變數, 表明C的所有子類都應 使用C相同的 構造器簽名 及 類方法簽名, 假如我們需要返回泛型型別的話, 需要用到

    更多使用方法, 見: typing使用

  2. 使用了pydantic規範要建立資料的型別

    關於pydantic的一般使用, 見: Pydantic使用

from typing import TypeVar, Type
from sqlalchemy.orm import Session
from pydantic import BaseModel
from orm.models import Category
from orm.schemas import WriteCategoryModel

# 定義型別
ModelT = TypeVar("ModelT")
DataT = TypeVar("DataT", bound=BaseModel)  # bound表明該類為BaseModel的子類

"""
為節省空間, 表結構和模型不展示
"""
def create_category(session: Session, data: WriteCategoryModel) -> Type[Category]:
    cate_obj = save_one_to_db(session=session, model_class=Category, data=data)
    return cate_obj


def save_one_to_db(session: Session, model_class: ModelT, data: DataT) -> ModelT:
    """
    儲存一條到資料庫
    :param session: SQLAlchemy Session
    :param model_class: sqlalchemy模型類
    :param data: pydantic模型物件
    :return: 對應sqlalchemy模型類的物件
    """
    try:
        obj = model_class(**data.dict())
        session.add(obj)
        session.commit()
        # 手動將 資料 重新整理到資料庫
        session.refresh(obj)
        return obj

    except Exception as e:
        # 別忘記發生錯誤時回滾
        session.rollback()
        raise e

pydantic使用

在使用pydantic時, 亦可以使用泛型

比如: 在FastAPI中返回統一返回格式為:

{
   "status": true,
   "msg": "success",
   "data": ...
}

我們的data可能是列表或物件, 而且對應的pydantic模型也不一樣, 這時我們就可以使用泛型了

程式碼:

from typing import List, Optional, Generic, TypeVar

from fastapi import APIRouter, status, HTTPException
from pydantic import BaseModel
from pydantic.generics import GenericModel

router = APIRouter(prefix="/test", tags=["測試泛型"])

# 為了方便, 在這裡定義pydantic模型
DataT = TypeVar("DataT")


class GenericResponse(GenericModel, Generic[DataT]):
    """
    通用返回資料
    """
    status: bool
    msg: str
    data: Optional[DataT] = None  # 可能連data都沒有
    # 設定response_model_exclude_unset=True即可


class BookModel(BaseModel):
    id: int
    name: str


# 偽資料
fake_book_data = [
    {"id": 1, "name": "book1"},
    {"id": 2, "name": "book2"},
    {"id": 3, "name": "book3"},
    {"id": 4, "name": "book4"},
    {"id": 5, "name": "book5"},
]


@router.get("/books", response_model=GenericResponse[List[BookModel]])
def get_books():
    return {
        "status": True,
        "msg": "獲取成功",
        "data": fake_book_data
    }


@router.get("/books/{bid}", response_model=GenericResponse[BookModel])
def retrieve_book(bid: int):
    for item in fake_book_data:
        if item.get("id") == bid:
            return {
                "status": True,
                "msg": "獲取成功",
                "data": item
            }
    # 不存在
    raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="該書不存在")

訪問/docs頁面, 成功通過測試
示意圖

相關文章