2.2 Metaclass 設計與應用
2.2 Metaclass 設計與應用
Metaclass 是「類別的類別」,控制類別本身的建立過程。這是 Python 中最深層的元編程機制。
先備知識
本章目標
學完本章後,你將能夠:
- 理解「類別也是物件」的概念
- 理解類別的建立流程
- 使用 Metaclass 控制類別行為
- 選擇適當的元編程工具
【原理層】類別也是物件
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 ────────┘
10 │
11 └── 42type() 的三參數形式
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__: 初始化 MyClasscall 控制實例建立
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__ |
| 修改類別的 namespace | Metaclass(__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
9│
10├── 需要控制實例建立?
11│ ├── 簡單快取 → __new__
12│ └── 複雜邏輯 → Metaclass.__call__
13│
14├── 需要記錄屬性定義順序?
15│ └── Metaclass.__prepare__
16│
17└── 只是想在實例上添加行為?
18 └── 用類別裝飾器(下一章)思考題
- 為什麼
type(type)是type自己?這是如何實現的? - 如果一個類別同時有 Metaclass 和
__init_subclass__,執行順序是什麼? - Django 的 Model 是如何使用 Metaclass 的?
實作練習
- 實作一個 Metaclass,自動為類別添加
__repr__方法 - 實作一個
@abstractmethod裝飾器和配套的 Metaclass - 使用
__init_subclass__實作一個事件處理器註冊系統
延伸閱讀
上一章:Descriptor Protocol 完整指南 下一章:類別裝飾器與動態類別