上篇我们知道了:
type
是一个类工厂,所有类都是它创建的。
class
语句本质上就是一次
type()
调用。
那如果我们
自己写一个类工厂,替掉type
,会怎样?
第一个元类 元类就是一个继承了
type
的类。
你重写它的
__new__
方法,就能在类创建的时候插一脚:
class SimpleMeta(type):
def __new__(mcs, name, bases, namespace):
print(f" 正在创建类: {name}")
cls = super().__new__(mcs, name, bases, namespace)
return cls
class MyClass(metaclass=SimpleMeta):
pass
# 输出: 正在创建类: MyClass
print(type(MyClass)) # —— 不再是 type
就这么简单。
metaclass=SimpleMeta
告诉 Python:「创建
MyClass
的时候,别用默认的
type()
,用我的
SimpleMeta()
」。
你可以多创建几个类,看看效果:
class Another(metaclass=SimpleMeta):
pass
class AndAnother(metaclass=SimpleMeta):
pass
# 输出:
# 正在创建类: Another
# 正在创建类: AndAnother
每一个用
SimpleMeta
创建的类,都会经过
__new__
。
这就是元类的核心—— 拦截类的创建过程 。
参数是什么意思?
__new__
里的四个参数,和
type()
的三个参数一一对应: 参数含义来源
mcs元类本身Python 自动传入name正在创建的类名对应type()的第一个参数bases父类元组对应type()的第二个参数namespace类体中定义的所有属性和方法的字典对应type()的第三个参数
你可以把
namespace
打印出来看看:
class DebugMeta(type):
def __new__(mcs, name, bases, namespace):
print(f" namespace = {namespace}")
return super().__new__(mcs, name, bases, namespace)
class Sample(metaclass=DebugMeta):
x = 10
def hello(self):
return "hi"
# 输出:
# namespace = {'__module__': '__main__', '__qualname__': 'Sample', 'x': 10, 'hello': }
x = 10
和
def hello
都在字典里。
你可以读它、改它、甚至拒绝创建这个类。
实战一:自动注入属性 理解了参数,我们来做点实用的事情。
假设你有一批类都需要一个
version
属性,但你不想每个类都手动写:
class AutoAttrMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if not hasattr(cls, 'version'):
cls.version = "1.0"
return cls
class Service(metaclass=AutoAttrMeta):
pass
class AdvancedService(Service):
version = "2.0" # 自己定义了,不会被覆盖
class BasicService(metaclass=AutoAttrMeta):
pass
print(Service.version) # 1.0 ← 自动加上了
print(AdvancedService.version) # 2.0 ← 自己定义的,没被覆盖
print(BasicService.version) # 1.0 ← 也自动加上了
逻辑很简单:类创建完之后,检查一下有没有
version
,没有就补上。
这就是元类最典型的用法—— 在类创建的那一刻,自动做一些事情 。
Django 的 Model 类就是这么干的:你写
class User(models.Model):
的时候,元类在背后自动帮你加了
__tablename__
、
__fields__
一堆东西,你根本不用操心。
实战二:在类定义时就拦住错误 元类还能做校验。
与其让代码跑到一半才发现字段名写错了,不如在类定义的时候就报错:
class LowercaseMeta(type):
def __new__(mcs, name, bases, namespace):
for attr_name in namespace:
if not attr_name.startswith('_') and attr_name != attr_name.lower():
raise TypeError(
f"类 '{name}' 中的属性 '{attr_name}' 必须全小写!
"
)
return super().__new__(mcs, name, bases, namespace)
class GoodClass(metaclass=LowercaseMeta):
my_field = 1 # ✓ 小写
another_field = 2 # ✓ 小写
print(GoodClass.my_field) # 1 —— 没问题
try:
class BadClass(metaclass=LowercaseMeta):
BadField = 1 # ✗ 大写了!
except TypeError as e:
print(e) # 类 'BadClass' 中的属性 'BadField' 必须全小写!
BadField
在类定义的那一刻就被拦下来了。
这个错误不会等到你调用某个方法时才冒出来——它在模块被 import 的时候就会发生,非常早。
实战三:单例模式 单例的意思是:一个类只能有一个实例。
不管你调用多少次
Database()
,拿到的都是同一个对象。
要实现这个,我们需要拦截的不是类的创建,而是 实例的创建 。
上篇我们讲过,当你写
Foo()
的时候,Python 实际上是在调用
type.__call__()
。
这个
__call__
方法会依次调用类的
__new__
和
__init__
。
元类可以重写这个
__call__
,在实例化时插入自己的逻辑:
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
print(f" 创建 {cls.__name__} 的唯一实例")
cls._instances[cls] = super().__call__(*args, **kwargs)
else:
print(f" 返回已有实例")
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
def __init__(self):
self.connection_id = id(self)
db1 = Database() # 创建 Database 的唯一实例
db2 = Database() # 返回已有实例
print(db1 is db2) # True —— 同一个对象
print(db1.connection_id == db2.connection_id) # True
调用链是这样的:
Database()
│
▼
SingletonMeta.__call__(Database) ← 元类拦截
│
├─ 第一次 → super().__call__() → Database.__new__() → Database.__init__()
│
└─ 之后 → 直接返回缓存的实例
注意:元类的
__call__
拦截的是
类()
这个调用,而类的
__new__
和
__init__
负责实际创建和初始化实例。
元类站在它们「上面」,决定要不要调用它们。
实战四:插件自动注册 最后一个实战例子——插件自动注册。
你定义一个子类,它就自动出现在注册表里,不需要手动调用任何注册函数:
class PluginMeta(type):
registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if name != "Base": # 跳过基类本身
mcs.registry[name] = cls
return cls
class Base(metaclass=PluginMeta):
pass
class AuthPlugin(Base):
def authenticate(self):
return "authenticated"
class LogPlugin(Base):
def log(self, msg):
return f"LOG: {msg}"
# 不需要手动注册,定义类的时候就完成了
print(list(PluginMeta.registry.keys())) # ['AuthPlugin', 'LogPlugin']
# 可以通过名字动态实例化
plugin = PluginMeta.registry["AuthPlugin"]()
print(plugin.authenticate()) # authenticated
这种模式在实际项目中非常常见:插件系统、策略模式、序列化框架按类型名找处理器……本质上都是「定义即注册」。
一个关键的细节:执行时机 元类的代码,在什么时候跑?
在类定义的时候,不是实例化的时候。
class TraceMeta(type):
def __new__(mcs, name, bases, namespace):
print(f" [元类] 正在创建 {name}")
return super().__new__(mcs, name, bases, namespace)
print("开始定义类...")
class A(metaclass=TraceMeta):
pass
print("类定义完成")
# 输出:
# 开始定义类...
# [元类] 正在创建 A
# 类定义完成
模块被 import 的那一刻,元类就已经跑完了。
之后你
A()
实例化的时候,元类的
__new__
不会再执行(但
__call__
会,如果你重写了的话)。
回顾一下
到这里你已经知道了:
元类就是一个继承type的类工厂
,重写
__new__
可以在类创建时做任何事情
重写__call__
可以拦截实例化,控制
__new__
和
__init__
是否被调用 元类代码在类定义时就执行 ,不是实例化时 但元类的能力不止于此。
它还能控制类的命名空间、自定义
isinstance
的行为、处理继承链上的元类冲突…… 下一篇,我们讲这些进阶内容,以及一个更重要的问题:什么时候不该用元类。
