Metaclass 是「類別的類別」,控制類別本身的建立過程。這是 Python 中最深層的元編程機制。

先備知識

本章目標

學完本章後,你將能夠:

  1. 理解「類別也是物件」的概念
  2. 理解類別的建立流程
  3. 使用 Metaclass 控制類別行為
  4. 選擇適當的元編程工具

【原理層】類別也是物件

type 是所有類別的 Metaclass

在 Python 中,類別本身也是物件:

 1class MyClass:
 2    pass
 3
 4# 類別是 type 的實例
 5print(type(MyClass))  # <class 'type'>
 6print(isinstance(MyClass, type))  # True
 7
 8# 物件是類別的實例
 9obj = MyClass()
10print(type(obj))  # <class '__main__.MyClass'>
11print(isinstance(obj, MyClass))  # True
 1關係圖:
 2
 3type ─────────────┐
 4  │               │
 5  ├── MyClass ────┤
 6  │     │         │ (都是 type 的實例)
 7  │     └── obj   │
 8  │               │
 9  └── int ────────┘
1011        └── 42

type() 的三參數形式

type() 可以動態建立類別:

 1# 這兩種方式等價
 2class MyClass:
 3    x = 10
 4    def method(self):
 5        return self.x
 6
 7# 等價於
 8def method(self):
 9    return self.x
10
11MyClass = type('MyClass', (), {'x': 10, 'method': method})

參數說明:

  • 第一個參數:類別名稱
  • 第二個參數:父類別的 tuple
  • 第三個參數:屬性和方法的 dict
 1# 繼承的例子
 2class Parent:
 3    def greet(self):
 4        return "Hello"
 5
 6Child = type('Child', (Parent,), {'name': 'Child Class'})
 7
 8c = Child()
 9print(c.greet())  # Hello
10print(c.name)     # Child Class

【設計層】類別建立過程

建立流程

當 Python 執行 class 語句時,會依序呼叫這些方法:

1class MyClass(Parent, metaclass=Meta):
2    ...
3
4執行流程:
51. Meta.__prepare__(name, bases) → 返回類別的 namespace(通常是 dict)
62. 執行類別主體,填充 namespace
73. Meta.__new__(mcs, name, bases, namespace) → 建立類別物件
84. Meta.__init__(cls, name, bases, namespace) → 初始化類別物件
 1class LoggingMeta(type):
 2    @classmethod
 3    def __prepare__(mcs, name, bases):
 4        print(f"1. __prepare__: 準備 {name} 的 namespace")
 5        return {}
 6
 7    def __new__(mcs, name, bases, namespace):
 8        print(f"2. __new__: 建立 {name}")
 9        return super().__new__(mcs, name, bases, namespace)
10
11    def __init__(cls, name, bases, namespace):
12        print(f"3. __init__: 初始化 {name}")
13        super().__init__(name, bases, namespace)
14
15class MyClass(metaclass=LoggingMeta):
16    x = 10
17    print("   (執行類別主體)")
18
19# 輸出:
20# 1. __prepare__: 準備 MyClass 的 namespace
21#    (執行類別主體)
22# 2. __new__: 建立 MyClass
23# 3. __init__: 初始化 MyClass

call 控制實例建立

Metaclass 的 __call__ 控制類別被呼叫時的行為:

 1class SingletonMeta(type):
 2    _instances = {}
 3
 4    def __call__(cls, *args, **kwargs):
 5        if cls not in cls._instances:
 6            cls._instances[cls] = super().__call__(*args, **kwargs)
 7        return cls._instances[cls]
 8
 9class Singleton(metaclass=SingletonMeta):
10    def __init__(self, value):
11        self.value = value
12
13s1 = Singleton(1)
14s2 = Singleton(2)
15print(s1 is s2)  # True
16print(s1.value)  # 1(第二次初始化被跳過)

【設計層】init_subclass - 輕量替代方案

Python 3.6 引入了 __init_subclass__,很多情況下可以替代 Metaclass:

 1class Plugin:
 2    _plugins = {}
 3
 4    def __init_subclass__(cls, plugin_name=None, **kwargs):
 5        super().__init_subclass__(**kwargs)
 6        name = plugin_name or cls.__name__
 7        cls._plugins[name] = cls
 8        print(f"註冊插件: {name}")
 9
10class EmailPlugin(Plugin, plugin_name="email"):
11    pass
12
13class SMSPlugin(Plugin, plugin_name="sms"):
14    pass
15
16print(Plugin._plugins)
17# {'email': <class 'EmailPlugin'>, 'sms': <class 'SMSPlugin'>}

何時用 init_subclass,何時用 Metaclass?

需求推薦方案
註冊子類別__init_subclass__
驗證類別屬性__init_subclass__
修改類別的 namespaceMetaclass(__prepare__
控制實例建立Metaclass(__call__
修改類別建立過程Metaclass
需要影響多層繼承Metaclass

【實作層】實用範例

自動註冊子類別

 1class Registry:
 2    _registry = {}
 3
 4    def __init_subclass__(cls, **kwargs):
 5        super().__init_subclass__(**kwargs)
 6        if cls.__name__ != 'Base':  # 跳過中間類別
 7            Registry._registry[cls.__name__] = cls
 8
 9    @classmethod
10    def get(cls, name):
11        return cls._registry.get(name)
12
13    @classmethod
14    def create(cls, name, *args, **kwargs):
15        klass = cls.get(name)
16        if klass:
17            return klass(*args, **kwargs)
18        raise ValueError(f"Unknown type: {name}")
19
20class Handler(Registry):
21    pass
22
23class JSONHandler(Handler):
24    def process(self, data):
25        return f"Processing JSON: {data}"
26
27class XMLHandler(Handler):
28    def process(self, data):
29        return f"Processing XML: {data}"
30
31# 使用
32handler = Registry.create("JSONHandler")
33print(handler.process("data"))  # Processing JSON: data

介面驗證

 1class InterfaceMeta(type):
 2    def __new__(mcs, name, bases, namespace):
 3        cls = super().__new__(mcs, name, bases, namespace)
 4
 5        # 跳過基類本身
 6        if bases:
 7            # 檢查是否實現了必要的方法
 8            required = getattr(cls, '_required_methods', [])
 9            for method in required:
10                if method not in namespace:
11                    raise TypeError(
12                        f"{name} 必須實現 {method} 方法"
13                    )
14        return cls
15
16class Serializable(metaclass=InterfaceMeta):
17    _required_methods = ['serialize', 'deserialize']
18
19# 這會引發 TypeError
20# class BadSerializer(Serializable):
21#     pass
22
23class GoodSerializer(Serializable):
24    def serialize(self, obj):
25        return str(obj)
26
27    def deserialize(self, data):
28        return eval(data)

使用 prepare 記錄定義順序

 1from collections import OrderedDict
 2
 3class OrderedMeta(type):
 4    @classmethod
 5    def __prepare__(mcs, name, bases):
 6        return OrderedDict()
 7
 8    def __new__(mcs, name, bases, namespace):
 9        cls = super().__new__(mcs, name, bases, dict(namespace))
10        cls._field_order = [
11            k for k in namespace.keys()
12            if not k.startswith('_')
13        ]
14        return cls
15
16class Form(metaclass=OrderedMeta):
17    name = "text"
18    email = "email"
19    age = "number"
20
21print(Form._field_order)  # ['name', 'email', 'age']

簡單的 ORM 基類

 1class Field:
 2    pass
 3
 4class CharField(Field):
 5    def __init__(self, max_length):
 6        self.max_length = max_length
 7
 8class IntegerField(Field):
 9    pass
10
11class ModelMeta(type):
12    def __new__(mcs, name, bases, namespace):
13        # 收集欄位
14        fields = {}
15        for key, value in namespace.items():
16            if isinstance(value, Field):
17                fields[key] = value
18
19        cls = super().__new__(mcs, name, bases, namespace)
20        cls._fields = fields
21        return cls
22
23class Model(metaclass=ModelMeta):
24    def __init__(self, **kwargs):
25        for name in self._fields:
26            setattr(self, name, kwargs.get(name))
27
28    def __repr__(self):
29        fields = ', '.join(
30            f"{k}={getattr(self, k)!r}"
31            for k in self._fields
32        )
33        return f"{self.__class__.__name__}({fields})"
34
35class User(Model):
36    name = CharField(max_length=100)
37    age = IntegerField()
38
39u = User(name="Alice", age=30)
40print(u)  # User(name='Alice', age=30)
41print(User._fields)  # {'name': CharField, 'age': IntegerField}

【選擇指南】元編程工具比較

 1問題:我需要修改類別的行為
 2
 3├── 只是想在子類別定義時做些事?
 4│   └── 用 __init_subclass__
 5 6├── 需要驗證或轉換類別屬性?
 7│   ├── 簡單驗證 → __init_subclass__
 8│   └── 需要修改 namespace → Metaclass
 910├── 需要控制實例建立?
11│   ├── 簡單快取 → __new__
12│   └── 複雜邏輯 → Metaclass.__call__
1314├── 需要記錄屬性定義順序?
15│   └── Metaclass.__prepare__
1617└── 只是想在實例上添加行為?
18    └── 用類別裝飾器(下一章)

思考題

  1. 為什麼 type(type)type 自己?這是如何實現的?
  2. 如果一個類別同時有 Metaclass 和 __init_subclass__,執行順序是什麼?
  3. Django 的 Model 是如何使用 Metaclass 的?

實作練習

  1. 實作一個 Metaclass,自動為類別添加 __repr__ 方法
  2. 實作一個 @abstractmethod 裝飾器和配套的 Metaclass
  3. 使用 __init_subclass__ 實作一個事件處理器註冊系統

延伸閱讀


上一章:Descriptor Protocol 完整指南 下一章:類別裝飾器與動態類別