跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Python 元类(中):拦截类的创建

上篇我们知道了:

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

的行为、处理继承链上的元类冲突…… 下一篇,我们讲这些进阶内容,以及一个更重要的问题:什么时候不该用元类。

相关文章