Python项目中5个Enum枚举巧妙操作详解
目录
- 用Enum替换字符串
- 值为整数的Enum
- 在数据库模型里使用E编程客栈num
- 给Enum添加方法
- 自动赋值的Enum
- 总结一下
刚开始在别人的代码里看到枚举,我其实觉得有点多余。用字符串或者数字常量不也一样吗?为啥非要整个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: http://www.devze.com print("工单当前状态:开启") elif state == TicketState.IN_PROGRESS: 编程 print("工单当前状态:处理中") elif state == TicketState.CLOSED: android 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项目中5个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_prodjsuction}") 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枚举的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论