Python項目中5個Enum枚舉巧妙操作詳解
剛開始在別人的代碼里看到枚舉,我其實覺得有點多余。用字符串或者數(shù)字常量不也一樣嗎?為啥非要整個Enum出來,感覺是把簡單問題復(fù)雜化了。但后來我接手了一個后端項目,里面有大量的常量定義,我才慢慢體會到Enum的好處。它不僅能讓代碼更整潔,還能強(qiáng)制保證代碼的一致性,避免了很多低級錯誤。
在開始之前,先提一嘴我的開發(fā)環(huán)境。我現(xiàn)在用的是ServBay,堪稱神器。
以前要部署Python環(huán)境,版本切來切去特別麻煩,尤其是有時候要維護(hù)一個Python 2的老項目,同時又要寫Python 3的新功能。ServBay能一鍵部署好Python環(huán)境,而且可以自由切換不同的Python版本,甚至讓多個版本同時運行。不管是Python 2.x的老古董還是最新的Python 3.x,它都能輕松搞定。

環(huán)境干凈又衛(wèi)生,我才能把心思花在琢磨代碼本身上,比如怎么用好Enum這種細(xì)節(jié)。
下面我就分享幾個我在實際項目里用過的Enum模式,沒什么高深理論,都是些實實在在幫我解決過問題的小技巧。
用Enum替換字符串
這是Enum最基礎(chǔ),也是最常見的用法。以前,我的代碼里可能有很多這樣的硬編碼字符串:
# 不推薦的寫法
def check_ticket_status(status_string):
if status_string == "OPEN":
print("工單開啟")
elif status_string == "IN_PROGRESS":
print("工單處理中")
elif status_string == "CLOSED":
print("工單已關(guān)閉")
這么寫也能跑,但問題很大。status參數(shù)可以傳入任何字符串,一不小心手滑,把CLOSED寫成了closed,這個條件就匹配不上了,bug就這么悄無聲息地出現(xiàn)了。
換成Enum之后,代碼就嚴(yán)謹(jǐn)多了:
from enum import Enum
class TicketState(Enum):
OPEN = "OPEN"
IN_PROGRESS = "IN_PROGRESS"
CLOSED = "CLOSED"
def handle_ticket(state: TicketState):
if state == TicketState.OPEN:
print("工單當(dāng)前狀態(tài):開啟")
elif state == TicketState.IN_PROGRESS:
print("工單當(dāng)前狀態(tài):處理中")
elif state == TicketState.CLOSED:
print("工單當(dāng)前狀態(tài):已關(guān)閉")
# 調(diào)用時必須使用Enum成員,非常安全
handle_ticket(TicketState.IN_PROGRESS)
現(xiàn)在,如果有人想傳入一個TicketState里沒有定義的值,靜態(tài)類型檢查工具(比如mypy)直接就會報警,代碼在運行前就能發(fā)現(xiàn)問題。這就從根源上杜絕了拼寫錯誤導(dǎo)致的bug。在處理后端服務(wù)各種復(fù)雜狀態(tài)流轉(zhuǎn)時,這個模式特別有用。
值為整數(shù)的Enum
我之前維護(hù)過一個很老的系統(tǒng),對接的API返回的狀態(tài)碼全是數(shù)字,比如1代表成功,2代表失敗。代碼里到處是if status_code == 1:這種數(shù)字,過段時間別說別人,我自己都看不懂了。
如果用Enum改造了這部分代碼,可讀性大大提高:
from enum import IntEnum
# 使用IntEnum,它繼承自int,行為更像整數(shù)
class ApiErrorCode(IntEnum):
OK = 0
INVALID_PARAMS = 1001
SERVER_ERROR = 2001
AUTH_FAILURE = 3001
def process_api_response(code: int):
try:
error_code = ApiErrorCode(code)
except ValueError:
print(f"收到未知的錯誤碼: [code]")
return
if error_code == ApiErrorCode.OK:
print("請求成功")
elif error_code == ApiErrorCode.SERVER_ERROR:
print("服務(wù)端內(nèi)部錯誤")
else:
print(f"業(yè)務(wù)錯誤: {error_code.name}")
# 示例調(diào)用
process_api_response(0)
process_api_response(3001)
process_api_response(9999) # 測試未知錯誤碼
這里用了IntEnum,它是Enum的一個變體,成員的值都是整數(shù),并且可以直接和整數(shù)進(jìn)行比較。通過ApiErrorCode(code)可以方便地把整數(shù)轉(zhuǎn)換為Enum成員,如果轉(zhuǎn)換失敗則說明收到了一個未定義的錯誤碼,邏輯清晰了很多。
在數(shù)據(jù)庫模型里使用Enum
在數(shù)據(jù)庫里存一些固定選項的字段時,Enum也特別好用。比如,文章的發(fā)布狀態(tài),可能只有“草稿”、“已發(fā)布”、“已歸檔”三種。直接在數(shù)據(jù)庫里存字符串當(dāng)然可以,但在代碼的模型層用Enum來約束會更安全。
from enum import Enum
from pydantic import BaseModel
from typing import List
class ArticleStatus(str, Enum):
DRAFT = "draft"
PUBLISHED = "published"
ARCHIVED = "archived"
class Article(BaseModel):
id: int
title: str
status: ArticleStatus
tags: List[str]
# 創(chuàng)建一個文章實例
new_article = Article(
id=101,
title="Python Enum使用技巧",
status=ArticleStatus.PUBLISHED,
tags=["python", "enum"]
)
# ArticleStatus.PUBLISHED可以直接作為字符串參與JSON序列化
print(new_article.model_dump_json())
# 輸出: {"id":101,"title":"Python Enum使用技巧","status":"published","tags":["python","enum"]}
注意這里的寫法:class ArticleStatus(str, Enum):。通過多繼承一個str,這個Enum的成員就擁有了字符串的所有特性。這樣做的好處是,它能和Pydantic、SQLAlchemy這類庫無縫集成,序列化成JSON或者存入數(shù)據(jù)庫時,它表現(xiàn)得就像一個普通的字符串,但在代碼層面,它又保留了Enum的類型安全和代碼補(bǔ)全等優(yōu)點。
給Enum添加方法
這個玩法是我后來才發(fā)現(xiàn)的,在Enum內(nèi)部直接定義方法。一開始我覺得挺奇怪的,Enum不是用來放常量的嗎?但如果某些邏輯和這個Enum強(qiáng)綁定,那把方法寫在里面就非常合適。
from enum import Enum
class Environment(Enum):
DEV = "development"
TEST = "testing"
PROD = "production"
@property
def is_production(self) -> bool:
return self == Environment.PROD
def get_db_url(self) -> str:
# 實際項目中,這里可能會從配置中讀取
db_urls = {
"development": "sqlite:///dev.db",
"testing": "sqlite:///test.db",
"production": "postgresql://user:pass@host/prod_db"
}
return db_urls[self.value]
# 使用起來非常直觀
current_env = Environment.PROD
print(f"當(dāng)前環(huán)境: {current_env.value}")
print(f"是否為生產(chǎn)環(huán)境: {current_env.is_production}")
print(f"數(shù)據(jù)庫連接: {current_env.get_db_url()}")
每個Environment的成員自己就知道對應(yīng)的數(shù)據(jù)庫連接,還能判斷自己是不是生產(chǎn)環(huán)境。這比在外面寫一堆if current_env == "production":要優(yōu)雅和內(nèi)聚得多。
自動賦值的Enum
有時候,Enum成員的名字和值是一樣的,一個個手動敲賦值會顯得很啰嗦。比如,我們要定義一組操作權(quán)限。
enum模塊里的auto()可以幫我們偷懶:
from enum import Enum, auto
class Permission(Enum):
# 重寫這個特殊方法,讓auto()返回成員的大寫字符串名
def _generate_next_value_(name, start, count, last_values):
return name.upper()
READ = auto()
WRITE = auto()
EXECUTE = auto()
DELETE = auto()
# 驗證一下
print(f"Read permission value: {Permission.READ.value}") # 輸出: Read permission value: READ
print(f"Delete permission value: {Permission.DELETE.value}") # 輸出: Delete permission value: DELETE
通過重寫內(nèi)部的_generate_next_value_方法,我們可以自定義auto()的行為。這里我讓它自動將成員名轉(zhuǎn)為大寫作為值,這樣定義Permission時就非常簡潔,完全不用寫等號右邊的部分。
總結(jié)一下
以上就是我實際在項目中用得比較多的五種Enum模式。剛開始我覺得Enum是形式主義,但隨著項目越來越復(fù)雜,我越來越喜歡用它。它讓代碼變得更嚴(yán)格,但也更清晰。
當(dāng)然,如果代碼很簡單,或者只有一兩個常量,我可能還是會直接用字符串。但對于中大型項目,特別是多人協(xié)作的時候,Enum真的能幫我們避免很多因為常量寫錯、不一致導(dǎo)致的傻瓜式錯誤。
總的來說,Enum不是什么高深的技巧,但用在對的地方,確實能讓代碼質(zhì)量上一個臺階。就像一個好用的開發(fā)工具 ServBay 能幫我們擺平環(huán)境配置的煩惱一樣,好的代碼習(xí)慣能讓我們在幾個月后回頭看自己的代碼時,少一些頭疼,多一份舒心。
以上就是Python項目中5個Enum枚舉巧妙操作詳解的詳細(xì)內(nèi)容,更多關(guān)于Python Enum枚舉的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
PyCharm GUI界面開發(fā)和exe文件生成的實現(xiàn)
這篇文章主要介紹了PyCharm GUI界面開發(fā)和exe文件生成,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Pandas之drop_duplicates:去除重復(fù)項方法
下面小編就為大家分享一篇Pandas之drop_duplicates:去除重復(fù)項方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-04-04
一篇文章搞懂Python Unittest測試方法的執(zhí)行順序
unittest是Python標(biāo)準(zhǔn)庫自帶的單元測試框架,是Python版本的JUnit,下面這篇文章主要給大家介紹了如何通過一篇文章搞懂Python Unittest測試方法的執(zhí)行順序,需要的朋友可以參考下2021-09-09
解決python 讀取excel時 日期變成數(shù)字并加.0的問題
這篇文章主要介紹了python 讀取excel時, 日期變成數(shù)字并加.0的問題,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2019-10-10
python實現(xiàn)按關(guān)鍵字篩選日志文件
今天小編大家分享一篇python實現(xiàn)按關(guān)鍵字篩選日志文件方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12

