Python使用Pydantic實現(xiàn)數(shù)據(jù)校驗的最佳實踐
?小張盯著屏幕上那一行報錯,已經(jīng)發(fā)了十分鐘的呆。
事情是這樣的。他們團隊在做一個用戶注冊功能,前端傳過來一份JSON數(shù)據(jù),按理說應(yīng)該有用戶名、郵箱、年齡三個字段。結(jié)果測試同學隨手填了個年齡“二十五”,程序直接炸了——類型錯誤,字符串不能和整數(shù)比較。
小張翻了翻代碼,發(fā)現(xiàn)校驗邏輯散落在各處:views.py里有一堆if判斷,models.py里又有幾個正則表達式,utils.py里還藏著個專門清洗數(shù)據(jù)的函數(shù)。改一個地方,另外兩個地方就忘了同步。
他嘆了口氣。這種問題,在這個項目里已經(jīng)出現(xiàn)過無數(shù)次了。
如果你也寫過Python后端,你一定懂這種感覺:數(shù)據(jù)校驗這件事,做起來不難,但做好很難。你永遠不知道用戶會傳什么亂七八糟的東西進來。今天是個字符串年齡,明天可能就是空的郵箱,后天直接少傳一個字段。
而且最煩的是,校驗代碼寫多了,業(yè)務(wù)邏輯反而看不清楚。一個函數(shù)里,前面二十行都在做類型檢查和判空,真正干活的代碼被擠到最后幾行。
我后來才知道,這個問題早就有了解法。它的名字叫Pydantic。
一、從一個最簡單的例子說起
先別急著看那些復(fù)雜的文檔。Pydantic最核心的東西,其實特別好理解。
假設(shè)你現(xiàn)在要寫一個用戶注冊的接口。傳統(tǒng)寫法大概是這樣:
def register_user(data):
if not data.get('username'):
raise ValueError('用戶名不能為空')
if not isinstance(data.get('age'), int):
raise ValueError('年齡必須是數(shù)字')
if data.get('age') < 18:
raise ValueError('年齡必須大于18歲')
# ... 繼續(xù)校驗郵箱、手機號等等
# 校驗通過后,才真正開始處理業(yè)務(wù)邏輯
這段代碼的問題不是它錯了,而是它把校驗和業(yè)務(wù)邏輯混在一起??创a的人需要一邊理解校驗規(guī)則,一邊理解業(yè)務(wù)邏輯,腦子很累。
用Pydantic改一下:
from pydantic import BaseModel, Field
class User(BaseModel):
username: str
age: int = Field(ge=18)
email: str
就這些。沒了。
當你需要校驗數(shù)據(jù)的時候:
def register_user(data):
user = User(**data)
# 校驗已經(jīng)自動完成,這里只管業(yè)務(wù)邏輯
save_to_database(user)
如果數(shù)據(jù)不符合要求,Pydantic會自動拋出ValidationError,并且告訴你哪里錯了。比如age傳了字符串"二十五",它會說"Input should be a valid integer"。如果age傳了16,它會說"Input should be greater than or equal to 18"。
這就是Pydantic最核心的價值:把校驗規(guī)則從業(yè)務(wù)代碼里抽離出來,讓代碼更干凈,讓錯誤信息更清晰。
二、自動類型轉(zhuǎn)換,幫你省掉無數(shù)if語句
你有沒有遇到過這種場景:前端傳過來的JSON里,所有數(shù)字都是字符串。你拿到數(shù)據(jù)之后,得挨個轉(zhuǎn)成整數(shù)或浮點數(shù),不然沒辦法做數(shù)值計算。
在Pydantic里,這事是自動的。
from pydantic import BaseModel
class Product(BaseModel):
price: float
quantity: int
data = {"price": "19.99", "quantity": "3"}
product = Product(**data)
print(product.price) # 19.99,已經(jīng)是float
print(product.quantity) # 3,已經(jīng)是int
只要字符串的內(nèi)容能安全地轉(zhuǎn)換成目標類型,Pydantic就幫你自動完成。轉(zhuǎn)換不了的時候,才會報錯。
這個特性在對接API的時候尤其好用。你不需要再寫一行一行的int(data['age']),也不需要擔心哪個字段忘記轉(zhuǎn)了。
三、可選字段和默認值,處理不完整數(shù)據(jù)
真實世界的數(shù)據(jù)很少是完整的。用戶可能不填手機號,API可能不返回某個字段。Pydantic處理這種情況也很簡單:
from pydantic import BaseModel
from typing import Optional
class UserProfile(BaseModel):
username: str
age: int
phone: Optional[str] = None
is_active: bool = True
Optional[str] = None 表示phone字段可以不存在,如果不存在就設(shè)為None。is_active = True 表示這個字段有默認值,調(diào)用方可以不傳。
這樣,當你接收到不完整的數(shù)據(jù)時,Pydantic會自動補全缺失的字段,而不是直接報錯。
data = {"username": "張三", "age": 25}
profile = UserProfile(**data)
print(profile.phone) # None
print(profile.is_active) # True
這在實際開發(fā)中非常實用。你不需要寫一堆data.get('phone', None)這樣的代碼,模型定義本身就是文檔。
四、Field約束,讓校驗規(guī)則一目了然
剛才我們用ge=18限制了年齡必須大于等于18。Field還提供了很多其他約束:
from pydantic import BaseModel, Field
class Product(BaseModel):
name: str = Field(min_length=1, max_length=100)
price: float = Field(gt=0, description="價格必須大于0")
rating: int = Field(ge=1, le=5)
tags: list[str] = Field(max_items=10)
這些約束寫在一起,比散落在各個地方的if語句好維護多了。你想改某個字段的校驗規(guī)則,只需要改模型定義那一行,不用在整個代碼庫里到處搜。
而且這些約束不只是運行時生效,還能自動生成API文檔。如果你用的是FastAPI,這些約束會自動映射到OpenAPI文檔里,前端的人看一眼就知道該怎么傳參數(shù)。
五、自定義校驗器,處理那些復(fù)雜邏輯
有些校驗規(guī)則不是簡單的大小比較能搞定的。比如手機號格式、密碼強度、兩個字段之間的依賴關(guān)系。
這時候可以用@field_validator:
from pydantic import BaseModel, field_validator
import re
class Account(BaseModel):
username: str
password: str
confirm_password: str
@field_validator('username')
def username_alphanumeric(cls, v):
if not v.isalnum():
raise ValueError('用戶名只能包含字母和數(shù)字')
if len(v) < 3:
raise ValueError('用戶名至少3個字符')
return v.lower() # 可以順便做規(guī)范化
@field_validator('confirm_password')
def passwords_match(cls, v, info):
if v != info.data.get('password'):
raise ValueError('兩次輸入的密碼不一致')
return v
注意第二個校驗器,它用到了info.data來獲取其他字段的值。這讓你可以校驗字段之間的依賴關(guān)系。
自定義校驗器里還能做數(shù)據(jù)清洗。比如用戶名統(tǒng)一轉(zhuǎn)小寫,電話號碼去掉橫線和空格。這樣后面用到這些數(shù)據(jù)的時候,已經(jīng)是最干凈的狀態(tài)了。
六、嵌套模型,處理復(fù)雜數(shù)據(jù)結(jié)構(gòu)
現(xiàn)實中的數(shù)據(jù)往往是嵌套的。一個訂單包含多個商品,每個商品又有自己的屬性。Pydantic處理這種嵌套非常自然:
from pydantic import BaseModel
from typing import List
class Address(BaseModel):
street: str
city: str
zip_code: str
class OrderItem(BaseModel):
product_id: int
quantity: int
price: float
class Order(BaseModel):
order_id: str
address: Address
items: List[OrderItem]
total: float
當你傳入嵌套的數(shù)據(jù)結(jié)構(gòu)時,Pydantic會遞歸地校驗每一層:
order_data = {
"order_id": "ORD-001",
"address": {"street": "123 Main St", "city": "Beijing", "zip_code": "100000"},
"items": [
{"product_id": 1, "quantity": 2, "price": 19.99},
{"product_id": 2, "quantity": 1, "price": 49.99}
],
"total": 89.97
}
order = Order(**order_data)
如果某個商品少了quantity字段,或者address里少了city,Pydantic會在對應(yīng)的層級報錯,告訴你具體是哪個位置出了問題。排查起來非常方便。
七、處理真實API響應(yīng)的技巧
在實際工作中,你經(jīng)常要處理各種API返回的數(shù)據(jù)。有些API返回的字段名和你代碼里用的不一樣,有些API返回的日期格式很奇怪。
Pydantic提供了field_alias和自定義校驗器來解決這些問題:
from pydantic import BaseModel, Field, field_validator
from datetime import datetime
class APIResponse(BaseModel):
user_id: int = Field(alias="id")
full_name: str = Field(alias="name")
created_at: datetime
@field_validator('created_at', mode='before')
def parse_date(cls, v):
# 處理各種奇怪的日期格式
if isinstance(v, str):
return v.replace('Z', '+00:00')
return v
alias讓你可以用API返回的字段名,但代碼里用自己習慣的名字。mode='before'的校驗器在類型轉(zhuǎn)換之前運行,最適合處理那些格式不統(tǒng)一的數(shù)據(jù)。
這樣,不管外部數(shù)據(jù)有多亂,到了你的業(yè)務(wù)代碼里,都是規(guī)規(guī)矩矩的Python對象。
八、性能怎么樣?
有人可能會擔心:加了這么多校驗,會不會變慢?
Pydantic的核心校驗邏輯是用Rust寫的(pydantic-core),比純Python實現(xiàn)快很多。官方文檔說,Pydantic V2比V1快了大約17倍。
在實際項目中,校驗的開銷通常遠小于數(shù)據(jù)庫查詢或網(wǎng)絡(luò)請求的開銷。所以放心用,它不是瓶頸。
九、寫在最后
小張后來把項目里的數(shù)據(jù)校驗全部重構(gòu)成了Pydantic模型。原來散落在各個文件里的校驗代碼,被幾十行模型定義替代了。代碼量減少了,可讀性提高了,bug也少了。
有一次新來的同事接手他的代碼,看完模型定義之后說:“原來這個字段有這些限制,一看就懂了。”
這就是Pydantic最大的價值——它讓數(shù)據(jù)校驗這件事,從“到處貼膠布”變成了“一次性定義清楚”。
數(shù)據(jù)校驗不該是代碼里的噪音,它應(yīng)該是代碼的一部分,清晰、簡潔、可維護。
如果你還在手動寫一堆if語句做校驗,不妨試試Pydantic。你會回來感謝它的。
到此這篇關(guān)于Python使用Pydantic實現(xiàn)數(shù)據(jù)校驗的最佳實踐的文章就介紹到這了,更多相關(guān)Python Pydantic數(shù)據(jù)校驗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python 專題二 條件語句和循環(huán)語句的基礎(chǔ)知識
本文主要介紹了Python條件語句和循環(huán)語句的基礎(chǔ)知識。主要內(nèi)容包括: 1.條件語句:包括單分支、雙分支和多分支語句,if-elif-else;2.循環(huán)語句:while的使用及簡單網(wǎng)絡(luò)刷博器爬蟲;3.循環(huán)語句:for的使用及遍歷列表、元組、文件和字符串。2017-03-03
教你怎么用Python處理excel實現(xiàn)自動化辦公
這篇文章主要介紹了教你怎么用Python處理excel實現(xiàn)自動化辦公,文中有非常詳細的代碼示例,對正在學習python的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
Python+Selenium實現(xiàn)表單自動填充和提交
你是不是也厭倦了每天重復(fù)表單填寫的工作,是時候讓技術(shù)來幫助我們解放雙手了,下面小編就為大家介紹一下如何使用Selenium和Python來自動填充和提交表單2023-09-09
詳解Python對某地區(qū)二手房房價數(shù)據(jù)分析
這篇文章主要為大家介紹了Python數(shù)據(jù)分析某地區(qū)二手房房價,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2021-12-12
Python實現(xiàn)爬蟲爬取NBA數(shù)據(jù)功能示例
這篇文章主要介紹了Python實現(xiàn)爬蟲爬取NBA數(shù)據(jù)功能,涉及Python針對URL模塊、字符串、列表遍歷、Excel寫入等相關(guān)操作技巧,需要的朋友可以參考下2018-05-05
DES加密解密算法之python實現(xiàn)版(圖文并茂)
這篇文章主要介紹了DES加密解密算法之python實現(xiàn)版,圖文并茂的為大家分享一下,需要的朋友可以參考下2018-12-12

