Python enum枚举消除魔术数字与IntEnum Flag
Python内置的enum模块用于把一组相关常量组织为具名成员,最直接的价值是替代状态码、类型码等“魔术数字”。原文示例中,如果代码写成 if status == 1、elif status == 2,可读性差;改用OrderStatus.PROCESSING这类枚举成员后,意图更明确,也能约束取值范围并减少拼写错误。一、创建与访问Enum
先定义Color枚举,成员RED=1、GREEN=2、BLUE=3。枚举成员可以通过属性、名字索引和值三种方式取得:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
print(Color.RED) # Color.RED
print(Color['RED']) # Color.RED
print(Color(1)) # Color.RED
成员的name和value分别保存名字和值:
print(Color.RED.name) # RED
print(Color.RED.value)# 1
print(Color.RED == Color.RED)# True
print(Color.RED == Color.BLUE) # False
print(Color.RED is Color.RED)# True
# print(Color.RED > Color.GREEN) # TypeError
普通Enum不支持大小比较,这一点与后续IntEnum不同。枚举可以直接迭代:
for color in Color:
print(f'{color.name} = {color.value}')
二、auto自动赋值
当不关心中间值,只想要连续编号时,可以用auto()。默认从1开始递增:
from enum import Enum, auto
class Status(Enum):
PENDING = auto()
APPROVED = auto()
REJECTED = auto()
CANCELLED = auto()
print([(s.name, s.value) for s in Status])
# [('PENDING', 1), ('APPROVED', 2), ('REJECTED', 3), ('CANCELLED', 4)]
如果需要自定义auto生成规则,可实现_generate_next_value_。原文用成员名的小写作为值:
class MyAuto(Enum):
def _generate_next_value_(name, start, count, last_values):
return name.lower()
class ConfigKey(MyAuto):
HOST = auto()
PORT = auto()
DEBUG = auto()
print([(k.name, k.value) for k in ConfigKey])
三、IntEnum:兼容整数比较
IntEnum继承整数能力,枚举成员既可作为枚举使用,也能当整数参与比较和运算:
from enum import IntEnum
class Priority(IntEnum):
LOW = 1
MEDIUM = 3
HIGH = 5
URGENT = 10
print(Priority.HIGH == 5) # True
print(Priority.HIGH > Priority.LOW) # True
print(Priority.HIGH + 1) # 6
注意,这种便利会牺牲一部分类型安全性。比如Priority.HIGH == 5为True,可能掩盖“把整数直接当枚举传”的意图。
四、Flag:位标志与权限组合
Flag适合表达可组合的权限、选项。auto()在Flag中按位生成1、2、4:
from enum import Flag, auto
class Permission(Flag):
NONE = 0
READ = auto() # 1
WRITE = auto() # 2
DELETE = auto()# 4
ADMIN = READ | WRITE | DELETE# 7
user_perms = Permission.READ | Permission.WRITE
print(user_perms) # Permission.READ|WRITE
print(Permission.READ in user_perms) # True
print(Permission.DELETE in user_perms) # False
user_perms |= Permission.DELETE
print(Permission.ADMIN in user_perms)# True
权限检查可以封装为函数,用“所需权限是否包含在用户权限中”判断:
def can_access(required, user_permission):
return required in user_permission
print(can_access(Permission.READ, Permission.READ | Permission.WRITE))
print(can_access(Permission.DELETE, Permission.READ | Permission.WRITE))
五、实战:HTTP状态码枚举
IntEnum很适合包装HTTP状态码。下面枚举包含200、201、400、401、403、404、500,并提供了判断成功和客户端错误的方法:
from enum import IntEnum
class HttpStatus(IntEnum):
OK = 200
CREATED = 201
BAD_REQUEST = 400
UNAUTHORIZED = 401
FORBIDDEN = 403
NOT_FOUND = 404
INTERNAL_ERROR = 500
def is_success(self):
return 200 <= self.value < 300
def is_client_error(self):
return 400 <= self.value < 500
def handle_response(status_code, body):
status = HttpStatus(status_code)
if status.is_success():
print(f'✓ {status.name}: {body}')
elif status.is_client_error():
print(f'✗ {status.name}: 客户端错误')
else:
print(f'⚠ {status.name}: 服务器错误')
handle_response(200, '操作成功')
handle_response(404, None)
六、实战:配置日志级别
普通Enum默认不支持>=比较,但可以通过实现__ge__来支持。原文示例将DEBUG、INFO、WARNING、ERROR分别设为10、20、30、40:
from enum import Enum
class LogLevel(Enum):
DEBUG = 10
INFO = 20
WARNING = 30
ERROR = 40
def __ge__(self, other):
if self.__class__ is other.__class__:
return self.value >= other.value
return NotImplemented
class LogConfig:
current_level = LogLevel.INFO
@classmethod
def set_level(cls, level):
if not isinstance(level, LogLevel):
raise TypeError(f'请使用LogLevel枚举,而不是 {type(level)}')
cls.current_level = level
@classmethod
def should_log(cls, level):
return level >= cls.current_level
LogConfig.set_level(LogLevel.DEBUG)
print(LogConfig.should_log(LogLevel.INFO))# True
七、选型与使用建议
通用枚举用Enum,重点是类型安全和名字清晰,不应与整数混用;需要兼容整数比较或运算时用IntEnum;需要位运算组合时用Flag;不关心具体值、只需自动编号时用auto()。成员命名通常采用全大写,类名采用PascalCase。代码中一旦出现0、1、2这类状态码,优先考虑用枚举表达。
Re: Python enum枚举消除魔术数字与IntEnum Flag
楼主这篇整理得挺清楚,把 Enum、IntEnum、Flag 放在一起对比很直观。平时写业务代码确实容易用 0、1、2 表示状态,过段时间自己都忘了含义,用枚举后可读性会好很多。名字索引、值索引、auto 和自定义生成值这些点也都很实用。 IntEnum 能直接和整数比较、运算,确实方便,但类型约束会松一些,这点提醒得很到位。Flag 那段用权限组合举例也很自然,READ | WRITE 和 in 判断比直接看位运算数字舒服多了。HTTP 状态码那个例子也很贴近实际,不过最后好像没贴完,is_client_error 应该就是判断 400 到 499 吧,期待楼主补全。整体很受用,收藏了。Re: Python enum枚举消除魔术数字与IntEnum Flag
感谢分享,整理得挺系统。用 OrderStatus.PROCESSING 这类枚举成员替代 if status == 1 这种魔术数字,确实在可读性和约束性上都更省心;auto 那段自定义 _generate_next_value_ 的用法也很实用。IntEnum 能直接和整数比较、参与运算虽然方便,但类型安全会弱一些,这个提醒很有必要。Flag 讲权限组合和 can_access 的写法也直观,很容易套到实际项目里。 另外看到最后 HttpStatus 的 is_client_error 方法好像断在“return 400Re: Python enum枚举消除魔术数字与IntEnum Flag
这篇整理得挺系统的,Enum 的基本创建和访问、auto 的用法、IntEnum 的整数兼容,以及 Flag 的权限组合都讲到了。普通 Enum 不支持大小比较这点很关键,换成 IntEnum 后代码会方便,但也要留意它和整数相等带来的类型模糊问题。Flag 里用 READ in user_perms 判断权限比直接位运算直观很多,封装 can_access 也很实用。HTTP 状态码用 IntEnum 包装并加 is_success 之类的方法,能让业务代码少很多魔术数字。最后 is_client_error 好像没贴完,方便的话可以补全,整体内容很受用,感谢分享。
页:
[1]