skills/enum-best-practices/SKILL.md
Python 枚举(Enum)最佳实践指南。涵盖枚举定义、命名规范、继承策略、序列化、数据库集成、模式匹配、Flag 位运算等全场景用法。当用户在 Python 项目中需要定义、使用或重构枚举类型时使用此技能。
npx skillsauth add cruldra/skills enum-best-practicesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
本技能提供 Python 枚举的全面使用指导,从基础定义到高级模式,覆盖 Web 框架集成、数据库 ORM、序列化、模式匹配等实战场景。
match/case 进行模式匹配str 或 int裸 Enum 的值不能直接序列化,也无法与字符串/整数直接比较。必须混入基础类型:
from enum import Enum
# ✅ 正确:继承 str, Enum
class Color(str, Enum):
RED = "red"
GREEN = "green"
BLUE = "blue"
# ✅ 正确:Python 3.11+ 使用 StrEnum(推荐)
from enum import StrEnum
class Color(StrEnum):
RED = "red"
GREEN = "green"
BLUE = "blue"
# ✅ 正确:整数枚举
from enum import IntEnum
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
# ❌ 错误:裸 Enum 无法直接序列化和比较
class Color(Enum):
RED = "red" # Color.RED == "red" 为 False!
str, Enum vs StrEnum 的区别# str, Enum:值是枚举成员,可以当 str 用
class Status(str, Enum):
ACTIVE = "active"
Status.ACTIVE == "active" # True
isinstance(Status.ACTIVE, str) # True
f"状态: {Status.ACTIVE}" # "状态: Status.ACTIVE" (Python 3.11+)
# "状态: active" (Python 3.10-)
# StrEnum (3.11+):行为一致且更明确
class Status(StrEnum):
ACTIVE = "active"
str(Status.ACTIVE) # "active"(StrEnum 保证 str() 返回值)
f"状态: {Status.ACTIVE}" # "状态: active"(始终一致)
建议:Python 3.11+ 项目用
StrEnum;需兼容旧版本用str, Enum。
auto() 自动生成值当枚举值本身不重要、只需要唯一标识时:
from enum import Enum, auto
# 整数自增(默认行为)
class Direction(Enum):
NORTH = auto() # 1
SOUTH = auto() # 2
EAST = auto() # 3
WEST = auto() # 4
# 自定义 auto() 生成逻辑
class LowerCaseEnum(str, Enum):
"""auto() 自动生成小写名称作为值"""
@staticmethod
def _generate_next_value_(name, start, count, last_values):
return name.lower()
class Color(LowerCaseEnum):
RED = auto() # "red"
GREEN = auto() # "green"
BLUE = auto() # "blue"
| 层级 | 规则 | 示例 |
|------|------|------|
| 枚举类名 | PascalCase,名词或形容词 | UserType, OrderStatus, Color |
| 枚举成员 | UPPER_SNAKE_CASE | ACTIVE, PENDING_REVIEW, IN_PROGRESS |
| 枚举值(字符串) | 小写蛇形 或 大写蛇形 | "active", "PENDING" |
| 数据库枚举名 | 小写 + _enum 后缀 | user_type_enum, order_status_enum |
值的大小写约定:
# 风格一:值与成员名一致(大写)—— 适合数据库枚举、状态机
class OrderStatus(str, Enum):
PENDING = "PENDING"
PAID = "PAID"
CANCELLED = "CANCELLED"
# 风格二:值为小写 —— 适合 API 响应、前端展示
class UserType(str, Enum):
NORMAL = "normal"
ADMIN = "admin"
AGENT = "agent"
选一种风格,全项目保持一致。
class Order(SQLModel, table=True):
"""订单表"""
class Status(str, Enum):
"""订单状态"""
PENDING = "PENDING"
PAID = "PAID"
CANCELLED = "CANCELLED"
REFUNDED = "REFUNDED"
class PaymentMethod(str, Enum):
"""支付方式"""
ALIPAY = "alipay"
WECHATPAY = "wechatpay"
status: Status = Field(default=Status.PENDING)
payment_method: PaymentMethod = Field(default=PaymentMethod.ALIPAY)
优点:枚举与模型紧密关联,引用清晰 Order.Status.PAID。
# enums.py 或 constants.py
class ProductType(str, Enum):
"""产品类型 —— 被 Order、PaymentPlan 等多个模型共享"""
APP = "app"
COURSE = "course"
DIGITAL_VIDEO = "digital_video"
TOOL = "tool"
| 场景 | 策略 | 理由 |
|------|------|------|
| 只在一个模型中使用 | 内嵌在模型类中 | 内聚性强,不污染模块命名空间 |
| 2+ 个模型共享 | 独立定义在 enums.py | 避免循环导入和重复定义 |
| 跨多个模块使用 | 独立定义 + 重新导出 | 在 __init__.py 中统一导出 |
class Status(str, Enum):
ACTIVE = "active"
INACTIVE = "inactive"
# 通过名称访问
Status["ACTIVE"] # Status.ACTIVE
Status.__members__ # {'ACTIVE': Status.ACTIVE, 'INACTIVE': Status.INACTIVE}
# 通过值访问
Status("active") # Status.ACTIVE
# 比较(str, Enum 支持直接与字符串比较)
Status.ACTIVE == "active" # True
Status.ACTIVE is Status.ACTIVE # True
# 遍历
for s in Status:
print(s.name, s.value) # ACTIVE active / INACTIVE inactive
# 成员检查
"active" in Status._value2member_map_ # True
# ❌ 危险:无效值会抛 ValueError
status = Status(user_input)
# ✅ 安全方式一:捕获异常
try:
status = Status(user_input)
except ValueError:
status = Status.ACTIVE # 回退默认值
# ✅ 安全方式二:使用 _missing_ 钩子
class Status(str, Enum):
ACTIVE = "active"
INACTIVE = "inactive"
@classmethod
def _missing_(cls, value):
"""处理未知值,返回默认成员或 None"""
for member in cls:
if member.value == str(value).lower():
return member
return None # 返回 None 会抛 ValueError
class HttpStatus(IntEnum):
OK = 200
NOT_FOUND = 404
SERVER_ERROR = 500
@property
def is_success(self) -> bool:
return 200 <= self.value < 300
@property
def is_error(self) -> bool:
return self.value >= 400
@property
def label(self) -> str:
labels = {
HttpStatus.OK: "成功",
HttpStatus.NOT_FOUND: "未找到",
HttpStatus.SERVER_ERROR: "服务器错误",
}
return labels.get(self, "未知状态")
# 使用
HttpStatus.OK.is_success # True
HttpStatus.NOT_FOUND.label # "未找到"
import json
class Status(str, Enum):
ACTIVE = "active"
INACTIVE = "inactive"
# str, Enum 可直接 json.dumps
json.dumps({"status": Status.ACTIVE}) # '{"status": "active"}'
# 裸 Enum 需要自定义编码器(这也是为什么要继承 str)
class EnumEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Enum):
return obj.value
return super().default(obj)
from pydantic import BaseModel, Field
class OrderCreate(BaseModel):
status: Status = Field(default=Status.ACTIVE, description="订单状态")
payment: Order.PaymentMethod = Field(
default=Order.PaymentMethod.ALIPAY,
description="支付方式"
)
# Pydantic v2 自动处理 str, Enum 的序列化
order = OrderCreate(status="active") # 字符串自动转枚举
order.model_dump() # {"status": "active", "payment": "alipay"}
from fastapi import APIRouter, Query
router = APIRouter()
@router.get("/orders")
async def list_orders(
status: Status = Query(default=Status.ACTIVE, description="筛选状态"),
):
"""FastAPI 自动生成 OpenAPI 枚举约束"""
return {"filter": status}
# Swagger UI 会自动显示下拉选项:active, inactive
from sqlalchemy import Column, Enum as SQLAlchemyEnum
from sqlmodel import Field, SQLModel
class User(SQLModel, table=True):
class UserType(str, Enum):
NORMAL = "normal"
AGENT = "agent"
user_type: UserType = Field(
default=UserType.NORMAL,
sa_column=Column(
SQLAlchemyEnum(UserType, name="user_type_enum"),
comment="用户类型",
),
)
关键点:
SQLAlchemyEnum 的 name 参数定义数据库中的枚举类型名,必须唯一小写_enum 后缀PostgreSQL 的枚举是独立的数据库类型,增删值需要专门的迁移处理。使用 alembic-postgresql-enum 扩展来自动化管理。
[project]
dependencies = [
"alembic>=1.14.0",
"alembic-postgresql-enum>=1.7.0",
]
在 env.py 中启用枚举变更检测:
# migrations/env.py
import alembic_postgresql_enum
alembic_postgresql_enum.set_configuration(
alembic_postgresql_enum.Config(
add_type_ignore=True, # 启用类型忽略注释
drop_unused_enums=True, # 清理未使用的枚举
detect_enum_values_changes=True, # 检测枚举值变更
)
)
def run_migrations_online():
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True, # 必须开启,否则检测不到枚举变更
)
# 生成迁移脚本(自动检测枚举变更)
python -m alembic revision --autogenerate -m "add new enum value"
# 应用迁移
python -m alembic upgrade head
alembic-postgresql-enum 提供的 sync_enum_values 方法可以安全地同步枚举值:
"""add knowledge-base to agent type enum"""
from alembic import op
from alembic_postgresql_enum import TableReference
def upgrade() -> None:
op.sync_enum_values(
enum_schema="public",
enum_name="agent_type_enum",
new_values=["CHAT", "AGENT_CHAT", "WORKFLOW", "COMPLETION", "KNOWLEDGE_BASE"],
affected_columns=[
TableReference(
table_schema="public",
table_name="qu_agents",
column_name="type",
)
],
enum_values_to_rename=[],
)
def downgrade() -> None:
op.sync_enum_values(
enum_schema="public",
enum_name="agent_type_enum",
new_values=["CHAT", "AGENT_CHAT", "WORKFLOW", "COMPLETION"],
affected_columns=[
TableReference(
table_schema="public",
table_name="qu_agents",
column_name="type",
)
],
enum_values_to_rename=[],
)
"""add digital_video enum value"""
from alembic import op
def upgrade() -> None:
op.execute("ALTER TYPE product_type_enum ADD VALUE IF NOT EXISTS 'DIGITAL_VIDEO'")
def downgrade() -> None:
# PostgreSQL 不支持删除枚举值,无法回滚
pass
sync_enum_values — 比原生 SQL 更安全,支持 upgrade/downgrade 双向迁移compare_type=True 必须开启 — 否则 --autogenerate 无法检测到枚举变更from django.db import models
class Order(models.Model):
class Status(models.TextChoices):
PENDING = "PENDING", "待处理"
PAID = "PAID", "已支付"
CANCELLED = "CANCELLED", "已取消"
status = models.CharField(
max_length=20,
choices=Status.choices,
default=Status.PENDING,
)
# 使用
order.status = Order.Status.PAID
order.get_status_display() # "已支付"
# SQLModel / SQLAlchemy
statement = select(Order).where(Order.status == Order.Status.PAID)
# Django
Order.objects.filter(status=Order.Status.PAID)
# ✅ 始终使用枚举常量
Order.objects.filter(status=Order.Status.PAID)
# ❌ 不要硬编码字符串
Order.objects.filter(status="PAID")
class Command(str, Enum):
START = "start"
STOP = "stop"
RESTART = "restart"
STATUS = "status"
def handle_command(cmd: Command) -> str:
match cmd:
case Command.START:
return "启动服务..."
case Command.STOP:
return "停止服务..."
case Command.RESTART:
return "重启服务..."
case Command.STATUS:
return "查询状态..."
case _:
return "未知命令"
注意:
match/case中必须使用Command.START全限定名,不能用裸START。
适用于需要组合多个选项的场景(权限、特性开关等):
from enum import Flag, auto
class Permission(Flag):
READ = auto() # 1
WRITE = auto() # 2
EXECUTE = auto() # 4
DELETE = auto() # 8
# 预定义组合
READ_WRITE = READ | WRITE
ADMIN = READ | WRITE | EXECUTE | DELETE
# 组合权限
user_perm = Permission.READ | Permission.WRITE
# 检查权限
Permission.READ in user_perm # True
Permission.EXECUTE in user_perm # False
# 添加/移除权限
user_perm |= Permission.EXECUTE # 添加
user_perm &= ~Permission.WRITE # 移除
# ❌ 运行时添加成员会报错
Color.YELLOW = "yellow" # AttributeError
# ⚠️ 同名但不同定义的枚举不相等
# module_a.py
class Status(str, Enum):
ACTIVE = "active"
# module_b.py
class Status(str, Enum):
ACTIVE = "active"
# module_a.Status.ACTIVE == module_b.Status.ACTIVE → False(不同类)
# module_a.Status.ACTIVE == "active" → True(值相等,因为继承了 str)
解决:枚举只定义一次,全项目从同一位置导入。
# ❌ 反模式:把枚举当字典用
class City(str, Enum):
BEIJING = "beijing"
SHANGHAI = "shanghai"
# ... 几百个城市
# ✅ 正确:用常规字典或数据库
CITIES = {"beijing": "北京", "shanghai": "上海"}
# ❌ 已有成员的枚举不能被继承
class Base(str, Enum):
A = "a"
class Child(Base): # TypeError!
B = "b"
# ✅ 可以继承没有成员的枚举基类
class LabeledEnum(str, Enum):
@property
def label(self) -> str:
return self.name.replace("_", " ").title()
class Status(LabeledEnum):
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
Status.IN_PROGRESS.label # "In Progress"
定义新枚举时逐项确认:
str, Enum(或 StrEnum / IntEnum)而非裸 EnumSQLAlchemyEnum 并指定 name 参数Field 添加 descriptiontesting
智能体 UAT 验收测试技能。用于验证智能体在真实场景下的表现是否满足预期。支持任意智能体框架(langchain、langgraph、deepagents、crewai 等)。触发词:测试智能体、验收测试、agent test、UAT
tools
Use when you need to create a Gitea issue, update its spec/plan markers, read or merge an issue's state JSON, or post a PR review comment in a repo that uses the spx CLI (superpowers-vscode workflow).
development
Use when implementing, modifying, refactoring, or reviewing code and the agent must follow explicit coding standards for simplicity, readability, maintainability, testability, project conventions, and minimal safe changes.
development
Use when integrating the deepagents SDK into a Python project — creating agents, configuring backends, adding subagents, middleware, memory, or skills. Also use when debugging deepagents agents or choosing between StateBackend, FilesystemBackend, and LocalShellBackend.