枚举类与元类入门
本教程共 70 篇 · 第 38 篇 · 更新于 2026-07-22 · 约 5 分钟阅读
38. 枚举类与元类入门
本节目标:学会用
Enum定义枚举,理解type()动态创建类的机制,对元类(metaclass)有初步认知。
这一章进入 OOP 的深水区。枚举类和元类都不是每天写业务代码必用的东西,但它们在框架设计、代码生成、状态管理等场景下非常有价值。
枚举类:Enum
状态值用整数或字符串表示,容易写错:
# 不好的写法
RED = 1
GREEN = 2
BLUE = 3
color = 4 # 写错了,但程序不会报错
Python 3.4+ 提供了 enum 模块,让你定义语义清晰的枚举:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
print(Color.RED) # Color.RED
print(Color.RED.name) # RED
print(Color.RED.value) # 1
Color.RED 是一个枚举成员,有 name 和 value 两个属性。它不等于整数 1:
print(Color.RED == 1) # False
print(Color.RED == Color.RED) # True
print(Color(1) == Color.RED) # True
Tip枚举成员是单例对象。同一个枚举值,无论怎么获取,得到的都是同一个对象。
Color(1)、Color.RED、Color['RED']三者等价。
枚举的遍历和比较
for c in Color:
print(c.name, c.value)
# RED 1
# GREEN 2
# BLUE 3
枚举成员之间可以用 is 比较,也可以用 ==。但不能做大小比较(除非用 IntEnum)。
自动赋值:auto()
如果枚举值本身不重要,可以用 auto() 自动分配:
from enum import Enum, auto
class Status(Enum):
PENDING = auto()
RUNNING = auto()
DONE = auto()
print(Status.PENDING.value) # 1
print(Status.RUNNING.value) # 2
IntEnum:值等于整数的枚举
有时候你需要枚举成员直接参与整数运算,比如跟数据库里的状态码对接。用 IntEnum:
from enum import IntEnum
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
print(Priority.LOW + 1) # 2
print(Priority(2)) # Priority.MEDIUM
Warning
IntEnum的成员等于整数,所以Priority.LOW == 1返回True。这会弱化类型安全,只在确实需要和整数混用的场景下使用。
枚举的典型用法
枚举最适合表示「有限且固定的状态集合」:
from enum import Enum, auto
class OrderState(Enum):
CREATED = auto()
PAID = auto()
SHIPPED = auto()
DELIVERED = auto()
CANCELLED = auto()
def handle_order(state: OrderState):
if state == OrderState.PAID:
print("通知仓库发货")
elif state == OrderState.SHIPPED:
print("发送物流信息给用户")
else:
print("无需处理")
handle_order(OrderState.PAID)
用枚举代替魔法数字,代码的可读性和可维护性都会提升。
type():类的类
我们一直说「类是创建实例的模板」。那类本身是什么创建的?答案是 type。
class Foo:
pass
print(type(Foo)) # <class 'type'>
type 是所有类的类,也叫「元类(metaclass)」。
更有趣的是,type 还可以当函数用,动态创建类:
def greet(self):
print(f"Hello, I'm {self.name}")
Person = type('Person', (), {'name': '匿名', 'greet': greet})
p = Person()
p.greet() # Hello, I'm 匿名
type(name, bases, dict) 的三个参数:
name:类名(字符串)。bases:父类元组。dict:类属性和方法的字典。
这等价于:
class Person:
name = '匿名'
def greet(self):
print(f"Hello, I'm {self.name}")
Note动态创建类在框架开发中很常见。比如 ORM(对象关系映射)库,会根据数据库表结构自动生成对应的 Python 类。背后用的就是
type()或元类。
元类入门
元类是「创建类的类」。默认的元类是 type。你可以自定义元类,在类创建时插入自己的逻辑。
自定义元类需要继承 type,并重写 __new__ 方法:
class AutoReprMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# 自动给没有 __repr__ 的类添加一个
if '__repr__' not in namespace:
def auto_repr(self):
attrs = ', '.join(f"{k}={v!r}" for k, v in self.__dict__.items())
return f"{name}({attrs})"
cls.__repr__ = auto_repr
return cls
class Point(metaclass=AutoReprMeta):
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p) # Point(x=1, y=2)
metaclass=AutoReprMeta 告诉 Python:创建 Point 类时,不要用默认的 type,而用我自定义的元类。元类的 __new__ 在类对象被创建时执行,此时实例还不存在。
Warning元类是 Python 最复杂的特性之一。日常开发几乎用不到,但在写框架、ORM、API 生成器时,它能大幅减少重复代码。如果你现在觉得晕,完全正常。记住两点就行:1) 类是
type的实例;2) 自定义元类可以干预类的创建过程。
一个小实战:用元类做接口检查
假设你要求所有「服务类」都必须实现 run 方法。可以用元类在类定义时就检查:
class ServiceMeta(type):
def __new__(mcs, name, bases, namespace):
if bases and 'run' not in namespace:
raise TypeError(f"{name} 必须实现 run 方法")
return super().__new__(mcs, name, bases, namespace)
class BaseService(metaclass=ServiceMeta):
pass
class EmailService(BaseService):
def run(self):
print("发送邮件")
# class BadService(BaseService):
# pass # TypeError: BadService 必须实现 run 方法
这比运行时才报错要好得多。问题在定义阶段就暴露,符合「fail fast」原则。
小结
Enum让状态值类型安全、语义清晰,替代魔法数字。type(name, bases, dict)可以动态创建类。- 元类是创建类的类,默认是
type。 - 自定义元类通过
metaclass=参数指定,能在类创建时执行额外逻辑。
枚举是日常开发就能用到的工具,元类则是进入框架开发领域的钥匙。两者都体现了 Python 的哲学:一切皆对象,连类本身也不例外。
来源:参考了 runoob「Python3 面向对象」、liaoxuefeng「使用枚举类」和「使用元类」、w3cschool「Python3 面向对象」等,改写后所得。