刚开始在别人的代码里看到枚举,我其实觉得有点多余。用字符串或者数字常量不也一样吗?为啥非要整个enum出来,感觉是把简单问题复杂化了。但后来我接手了一个后端项目,里面有大量的常量定义,我才慢慢体会到enum的好处。它不仅能让代码更整洁,还能强制保证代码的一致性,避免了很多低级错误。
在开始之前,先提一嘴我的开发环境。我现在用的是servbay,堪称神器。
以前要部署python环境,版本切来切去特别麻烦,尤其是有时候要维护一个python 2的老项目,同时又要写python 3的新功能。servbay能一键部署好python环境,而且可以自由切换不同的python版本,甚至让多个版本同时运行。不管是python 2.x的老古董还是最新的python 3.x,它都能轻松搞定。

环境干净又卫生,我才能把心思花在琢磨代码本身上,比如怎么用好enum这种细节。
下面我就分享几个我在实际项目里用过的enum模式,没什么高深理论,都是些实实在在帮我解决过问题的小技巧。
用enum替换字符串
这是enum最基础,也是最常见的用法。以前,我的代码里可能有很多这样的硬编码字符串:
# 不推荐的写法
def check_ticket_status(status_string):
if status_string == "open":
print("工单开启")
elif status_string == "in_progress":
print("工单处理中")
elif status_string == "closed":
print("工单已关闭")
这么写也能跑,但问题很大。status参数可以传入任何字符串,一不小心手滑,把closed写成了closed,这个条件就匹配不上了,bug就这么悄无声息地出现了。
换成enum之后,代码就严谨多了:
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("工单当前状态:开启")
elif state == ticketstate.in_progress:
print("工单当前状态:处理中")
elif state == ticketstate.closed:
print("工单当前状态:已关闭")
# 调用时必须使用enum成员,非常安全
handle_ticket(ticketstate.in_progress)
现在,如果有人想传入一个ticketstate里没有定义的值,静态类型检查工具(比如mypy)直接就会报警,代码在运行前就能发现问题。这就从根源上杜绝了拼写错误导致的bug。在处理后端服务各种复杂状态流转时,这个模式特别有用。
值为整数的enum
我之前维护过一个很老的系统,对接的api返回的状态码全是数字,比如1代表成功,2代表失败。代码里到处是if status_code == 1:这种数字,过段时间别说别人,我自己都看不懂了。
如果用enum改造了这部分代码,可读性大大提高:
from enum import intenum
# 使用intenum,它继承自int,行为更像整数
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("服务端内部错误")
else:
print(f"业务错误: {error_code.name}")
# 示例调用
process_api_response(0)
process_api_response(3001)
process_api_response(9999) # 测试未知错误码
这里用了intenum,它是enum的一个变体,成员的值都是整数,并且可以直接和整数进行比较。通过apierrorcode(code)可以方便地把整数转换为enum成员,如果转换失败则说明收到了一个未定义的错误码,逻辑清晰了很多。
在数据库模型里使用enum
在数据库里存一些固定选项的字段时,enum也特别好用。比如,文章的发布状态,可能只有“草稿”、“已发布”、“已归档”三种。直接在数据库里存字符串当然可以,但在代码的模型层用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]
# 创建一个文章实例
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或者存入数据库时,它表现得就像一个普通的字符串,但在代码层面,它又保留了enum的类型安全和代码补全等优点。
给enum添加方法
这个玩法是我后来才发现的,在enum内部直接定义方法。一开始我觉得挺奇怪的,enum不是用来放常量的吗?但如果某些逻辑和这个enum强绑定,那把方法写在里面就非常合适。
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"当前环境: {current_env.value}")
print(f"是否为生产环境: {current_env.is_production}")
print(f"数据库连接: {current_env.get_db_url()}")
每个environment的成员自己就知道对应的数据库连接,还能判断自己是不是生产环境。这比在外面写一堆if current_env == "production":要优雅和内聚得多。
自动赋值的enum
有时候,enum成员的名字和值是一样的,一个个手动敲赋值会显得很啰嗦。比如,我们要定义一组操作权限。
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
通过重写内部的_generate_next_value_方法,我们可以自定义auto()的行为。这里我让它自动将成员名转为大写作为值,这样定义permission时就非常简洁,完全不用写等号右边的部分。
总结一下
以上就是我实际在项目中用得比较多的五种enum模式。刚开始我觉得enum是形式主义,但随着项目越来越复杂,我越来越喜欢用它。它让代码变得更严格,但也更清晰。
当然,如果代码很简单,或者只有一两个常量,我可能还是会直接用字符串。但对于中大型项目,特别是多人协作的时候,enum真的能帮我们避免很多因为常量写错、不一致导致的傻瓜式错误。
总的来说,enum不是什么高深的技巧,但用在对的地方,确实能让代码质量上一个台阶。就像一个好用的开发工具 servbay 能帮我们摆平环境配置的烦恼一样,好的代码习惯能让我们在几个月后回头看自己的代码时,少一些头疼,多一份舒心。
以上就是python项目中5个enum枚举巧妙操作详解的详细内容,更多关于python enum枚举的资料请关注代码网其它相关文章!
发表评论