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

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

