元类
问题
什么是元类?type 和 metaclass 的关系是什么?元类有哪些实际应用?
答案
类也是对象
在 Python 中,类本身就是对象,它由元类创建。默认元类是 type:
class MyClass:
pass
print(type(MyClass)) # <class 'type'>
print(type(type)) # <class 'type'> — type 自身的类型也是 type
print(isinstance(MyClass, type)) # True
所以关系链是:type → 创建 → class → 创建 → instance
type 动态创建类
# 常规方式
class Dog:
sound = "汪汪"
def speak(self):
return self.sound
# 等价的 type() 方式
Dog = type("Dog", (object,), {
"sound": "汪汪",
"speak": lambda self: self.sound,
})
d = Dog()
print(d.speak()) # 汪汪
type(name, bases, attrs) 三个参数:类名、基类元组、属性字典。
自定义元类
class SingletonMeta(type):
"""元类实现单例模式"""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
# 调用 type.__call__ 创建实例
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
def __init__(self):
self.connection = "connected"
db1 = Database()
db2 = Database()
print(db1 is db2) # True
__init_subclass__ — 元类的轻量替代
Python 3.6+ 提供了 __init_subclass__,大多数场景下可以替代元类:
class Plugin:
_registry = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Plugin._registry[cls.__name__] = cls
class AudioPlugin(Plugin): pass
class VideoPlugin(Plugin): pass
print(Plugin._registry)
# {'AudioPlugin': <class 'AudioPlugin'>, 'VideoPlugin': <class 'VideoPlugin'>}
优先使用
__init_subclass__元类强大但复杂,90% 的场景用 __init_subclass__ 或装饰器更简单。只在需要控制类创建过程本身时才用元类。
常见面试问题
Q1: __new__ 在元类中的作用?
答案:
元类的 __new__ 在类(不是实例)被创建时调用,可以修改类的属性和行为:
class UpperAttrMeta(type):
def __new__(mcs, name, bases, attrs):
# 将所有非魔术方法的属性名转为大写
upper_attrs = {}
for key, val in attrs.items():
if not key.startswith("__"):
upper_attrs[key.upper()] = val
else:
upper_attrs[key] = val
return super().__new__(mcs, name, bases, upper_attrs)
class Foo(metaclass=UpperAttrMeta):
bar = "hello"
print(hasattr(Foo, "bar")) # False
print(hasattr(Foo, "BAR")) # True
print(Foo.BAR) # "hello"
Q2: ABCMeta 是什么?
答案:
ABCMeta 是 abc 模块提供的元类,用于定义抽象基类:
from abc import ABC, abstractmethod
# ABC 等价于 metaclass=ABCMeta
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
# Shape() → TypeError
Q3: 什么时候用元类?什么时候用装饰器?
答案:
| 场景 | 推荐方案 |
|---|---|
| 修改类属性/方法 | 装饰器或 __init_subclass__ |
| 注册子类 | __init_subclass__ |
| 验证类定义 | __init_subclass__ |
| 控制类的创建过程 | 元类 |
| ORM 字段映射 | 元类(如 Django Model) |
| 单例 | 元类或装饰器 |