2025-10-06 Python不基础 15——metaclass

文章目录

  • [1. `type`](#1. type)
  • [2. 元类定义与使用](#2. 元类定义与使用)
    • [2.1. 定义元类(继承自`type`)](#2.1. 定义元类(继承自type))
    • [2.2. 指定目标类使用元类(`metaclass`参数)](#2.2. 指定目标类使用元类(metaclass参数))
    • [2.3. "继承元类" vs "指定元类"](#2.3. “继承元类” vs “指定元类”)
  • [3. 元类核心方法](#3. 元类核心方法)
    • [3.1. `new`](#3.1. __new__)
    • [3.2. `init`](#3.2. __init__)
    • [3.3. `call`](#3.3. __call__)
  • [4. 应用场景](#4. 应用场景)
  • [5. `super`调用](#5. super调用)
  • [6. 总结](#6. 总结)

本文参考视频链接:

元类(Metaclass)是"创建类的类",用于自定义类的创建流程。

要理解元类,必须先回顾Python中"类的默认创建方式"------所有普通类(如class A:)的底层都是由type(Python内置元类)创建的。元类的本质就是替换默认的type,自定义类的创建逻辑

1. type

type是Python的"内置元类",所有普通类都是type实例(即"类是元类的对象")。

静态定义类(class A:)与动态调用type创建类完全等价type是默认的"类的创建者"(即内置元类)。

python 复制代码
# 1. 静态定义类A
class A:
    name = "AAA"
    def F(self):
        print(1)

# 2. 动态调用type创建类A(与上述静态定义完全等价)
def F(self):
    print(1)
A = type(
    "A",          # 类名(字符串)
    (),           # 父类元组(无父类时为空)
    {"name": "AAA", "F": F}  # 类属性/方法字典
)

元类的核心是"自定义类的创建逻辑 "------当我们不想用默认的type创建类时,可以定义一个继承自type的类(即元类),并指定目标类由这个元类创建。

元类的本质关系:实例 → 类 → 元类

Python中存在三层层级关系,这是理解元类的关键:

层级 角色 示例 关系描述
第1层 实例(Object) o = A()中的o oA(类)的实例
第2层 类(Class) class A:中的A AM(元类)的实例
第3层 元类(Metaclass) class M(type):中的M M继承自type,是"创建类的类"

小结:

  • 实例由类创建(o = A());
  • 类由元类创建(A = M(...),默认Mtype)。

2. 元类定义与使用

2.1. 定义元类(继承自type

元类必须继承自type(因为type是所有元类的"基类"),通过重载type的方法(如__new____init__)实现自定义逻辑。

python 复制代码
# 定义元类M,继承自type
class M(type):
    # 重载__new__方法:在类创建前执行,负责创建类对象
    def __new__(cls, name, bases, attrs):
        # cls:元类本身(即M)
        # name:目标类的类名(如"A")
        # bases:目标类的父类元组(如())
        # attrs:目标类的属性/方法字典(如{"name": "AAA", "F": F})
        print(f"类名:{name}")
        print(f"父类:{bases}")
        print(f"类属性:{attrs}")
        # 调用父类(type)的__new__,最终创建并返回类对象
        return super().__new__(cls, name, bases, attrs)

2.2. 指定目标类使用元类(metaclass参数)

通过在类定义时添加metaclass参数,指定该类由自定义元类(如M)创建,而非默认的type

python 复制代码
# 定义类A,指定元类为M(而非默认的type)
# 注意:不是"A继承M",而是"A由M创建"
class A(metaclass=M):
    name = "AAA"
    def F(self):
        print(1)

运行上述代码时,无需创建A的实例,就会打印以下内容:

复制代码
类名:A
父类:()
类属性:{'__module__': '__main__', '__qualname__': 'A', 'name': 'AAA', 'F': <function A.F at 0x...>}

结论:元类的__new__方法在目标类定义时自动调用(而非实例化时),负责创建类对象。

2.3. "继承元类" vs "指定元类"

"类继承元类"和"类指定元类"两者本质不同:

写法 含义 结果
class A(M): A继承M(M是普通类) A是M的子类,A的元类仍是默认的type
class A(metaclass=M): A由M创建(M是元类,继承自type) A是M的实例,A的元类是M

验证方式:查看类的元类(type(A)

python 复制代码
class M(type):
    pass

class A(metaclass=M):
    pass

class B(M):  # B继承M(M是元类,但B的元类仍是type)
    pass

print(type(A))  # 输出:<class '__main__.M'>(A的元类是M)
print(type(B))  # 输出:<class 'type'>(B的元类是默认的type)

3. 元类核心方法

3.1. __new__

  • 调用时机 :目标类(如A)定义时,元类首先调用__new__
  • 核心作用:创建类对象(即最终的A),可以在创建前修改类的属性/方法(如过滤非法属性);
  • 参数含义
    • cls:元类本身(如M);
    • name:目标类名(如"A");
    • bases:目标类的父类元组;
    • attrs:目标类的属性/方法字典;
  • 返回值 :必须返回调用父类__new__创建的类对象(如super().__new__(cls, name, bases, attrs))。

例如:过滤类中的非法方法,禁止类中出现test_开头的方法,否则报错。

python 复制代码
class M(type):
    def __new__(cls, name, bases, attrs):
        # 遍历类的所有属性/方法
        for attr_name, attr_value in attrs.items():
            # 判断是否是test_开头的函数
            if attr_name.startswith("test_") and callable(attr_value):
                raise TypeError(f"类{A}中禁止定义test_开头的方法:{attr_name}")
        # 创建并返回类对象
        return super().__new__(cls, name, bases, attrs)

# 测试:定义含test_方法的类,触发报错
class A(metaclass=M):
    def test_func(self):  # 非法方法
        pass

# 运行结果:TypeError: 类A中禁止定义test_开头的方法:test_func

3.2. __init__

  • 调用时机__new__创建类对象后,元类自动调用__init__
  • 核心作用:初始化已创建的类对象(如给类添加额外属性),此时类对象已存在;
  • 参数含义
    • cls:元类本身(如M);
    • name:目标类名(如"A");
    • bases:目标类的父类元组;
    • attrs:目标类的属性/方法字典;
  • 返回值:无需返回(或返回None),仅负责初始化。

例如:创建类时,自动给类添加random_id属性(0-100的随机数)。

python 复制代码
import random

class M(type):
    def __new__(cls, name, bases, attrs):
        # 先创建类对象
        new_class = super().__new__(cls, name, bases, attrs)
        return new_class  # 返回创建好的类对象
    
    def __init__(cls, name, bases, attrs):
        # 初始化类对象:添加random_id属性
        cls.random_id = random.randint(0, 100)
        super().__init__(name, bases, attrs)  # 调用父类初始化

# 测试:类A由M创建,自动拥有random_id
class A(metaclass=M):
    pass

print(A.random_id)  # 输出:随机整数(如42,每次运行不同)

__new____init__的区别

对比维度 __new__ __init__
调用时机 类创建前(先执行) 类创建后(后执行)
作用对象 待创建的类对象 已创建的类对象
核心任务 创建类对象 初始化类对象
返回值 必须返回类对象 无需返回(或返回None)

3.3. __call__

  • 调用时机 :当调用目标类创建实例时(如o = A()),元类的__call__方法被调用;
  • 核心作用:控制实例的创建流程(如实现单例模式:确保类只有一个实例);
  • 参数含义
    • cls:目标类(如A,即元类M的实例);
    • *args:创建实例时传入的位置参数(如A(1, 2)中的1, 2);
    • **kwargs:创建实例时传入的关键字参数;
  • 返回值 :通常返回创建好的实例(如super().__call__(*args, **kwargs))。

例如:确保类A只有一个实例,多次调用A()返回同一个对象。

python 复制代码
class M(type):
    # 用元类的属性存储实例(确保唯一)
    _instance = None

    def __call__(cls, *args, **kwargs):
        # 判断实例是否已存在
        if cls._instance is None:
            # 调用父类的__call__,创建实例(底层会调用A的__new__和__init__)
            cls._instance = super().__call__(*args, **kwargs)
        # 返回已存在的实例(确保唯一)
        return cls._instance

# 测试:类A由M创建,实现单例
class A(metaclass=M):
    pass

# 多次创建实例,实际是同一个对象
o1 = A()
o2 = A()
print(o1 is o2)  # 输出:True(o1和o2是同一个实例)

当执行o = A()时,实际调用流程是:

M.__call__(A, *args, **kwargs) → 内部调用A.__new__创建实例 → 调用A.__init__初始化实例 → 返回实例。

4. 应用场景

元类并非日常开发必需品,仅用于解决"多个类需要统一遵循某一规则"的场景------这类场景用继承难以实现(继承只能管控子类,元类可管控所有由它创建的类)。

常见应用场景总结:

  1. 统一管控类的属性/方法 :如禁止特定命名的方法(test_开头)、强制添加必需属性(如version);
  2. 实现设计模式:如单例模式(确保类只有一个实例)、工厂模式(统一创建实例的逻辑);
  3. 自动生成代码:如根据配置动态给类添加方法(无需手动定义);
  4. 框架级别的统一配置 :如Django的ORM中,用元类(ModelBase)统一管理模型类的创建,自动关联数据库表。

5. super调用

在元类的方法中使用super()时,__new____init__/__call__的写法不同,容易踩坑。

__new__中使用super():必须传cls

__new__是元类的静态方法(隐式),调用super()时需明确传入元类本身(cls)和当前类名(name可选,通常传cls):

python 复制代码
class M(type):
    def __new__(cls, name, bases, attrs):
        # 正确写法:super().__new__(cls, name, bases, attrs)
        return super().__new__(cls, name, bases, attrs)
        # 错误写法:super().__new__(name, bases, attrs)(缺少cls参数)

__init__/__call__中使用super():无需传cls

__init____call__是元类的实例方法,调用super()时无需额外传参(self会自动关联):

python 复制代码
class M(type):
    def __init__(cls, name, bases, attrs):
        # 正确写法:super().__init__(name, bases, attrs)
        super().__init__(name, bases, attrs)
        # 错误写法:super().__init__(cls, name, bases, attrs)(多传了cls参数)
    
    def __call__(cls, *args, **kwargs):
        # 正确写法:super().__call__(*args, **kwargs)
        return super().__call__(*args, **kwargs)
  • __new__负责创建类对象,需要明确知道"由哪个元类创建"(因此需传cls);
  • __init__/__call__负责初始化类对象/创建实例,此时元类已确定(self即元类实例),无需额外传参。

6. 总结

  1. 元类是"创建类的类" :普通类是元类的实例,默认元类是type
  2. 元类的关键方法
    • __new__:类创建前执行,创建类对象;
    • __init__:类创建后执行,初始化类对象;
    • __call__:实例化类时执行,创建实例;
  3. 元类的作用:解决"多个类统一管控"的问题,继承无法替代;
  4. 使用方式 :定义元类(继承type)→ 目标类用metaclass参数指定元类。

元类是Python面向对象的高级特性,掌握它能让你更深入理解Python的类模型,但无需过度追求------先保证基础功能的简洁性和可读性,再考虑用元类解决复杂场景。

相关推荐
Angel Q.1 天前
特征提取 | DINO 到 DINOv3
图像处理·python
不如语冰1 天前
AI大模型入门-pytorch-张量2 tensor创建
python
NPE~1 天前
[AI]Agent开发——ADK框架使用
人工智能·python·ai·教程·adk·agent开发
矮个史蒂芬1 天前
统计verilog .v文件中output 的bit数
python
gwf2161 天前
磨损均衡算法(Wear Leveling)——SSD如何让每块闪存“公平退休“?
运维·数据库·人工智能·python·嵌入式硬件·算法·智能硬件
爱吃提升1 天前
python 分布式爬虫、爬虫合规与综合实战项目
分布式·爬虫·python
审小匠OpenCPAi1 天前
银行流水核查怎么自动化?单边匹配、双向勾稽与图聚类异常检测的工程对比
java·前端·人工智能·python·审计
元Y亨H1 天前
Pandas 解析 Excel 导致的内存溢出(MemoryError)
python·excel
金銀銅鐵1 天前
[Python] 用 turtle 来绘制瑞典国旗
python
Albert Edison1 天前
【GUI 自动化测试】Pywinauto 常见操作
自动化测试·python·pywinauto