2.3 類別裝飾器與動態類別
2.3 類別裝飾器與動態類別
類別裝飾器是比 Metaclass 更簡單、更實用的元編程工具。大多數情況下,類別裝飾器就能滿足需求。
先備知識
本章目標
學完本章後,你將能夠:
- 編寫類別裝飾器
- 理解
@dataclass的實現原理 - 使用
type()動態建立類別 - 選擇合適的元編程工具
【原理層】類別裝飾器
基本結構
類別裝飾器是一個接收類別、返回類別的函式:
1def my_decorator(cls):
2 # 修改或增強 cls
3 return cls
4
5@my_decorator
6class MyClass:
7 pass
8
9# 等價於
10class MyClass:
11 pass
12MyClass = my_decorator(MyClass)與函式裝飾器的差異
1# 函式裝飾器通常返回包裝函式
2def func_decorator(func):
3 def wrapper(*args, **kwargs):
4 print("Before")
5 result = func(*args, **kwargs)
6 print("After")
7 return result
8 return wrapper
9
10# 類別裝飾器通常直接修改類別
11def class_decorator(cls):
12 cls.new_attribute = "added"
13 return cls執行時機
1def decorator(cls):
2 print(f"裝飾 {cls.__name__}")
3 return cls
4
5@decorator
6class MyClass:
7 print("定義類別主體")
8
9# 輸出:
10# 定義類別主體
11# 裝飾 MyClass類別裝飾器在類別定義完成後立即執行。
【設計層】帶參數的裝飾器
裝飾器工廠
1def repeat_method(times):
2 """讓指定方法重複執行 times 次"""
3 def decorator(cls):
4 original_init = cls.__init__
5
6 def new_init(self, *args, **kwargs):
7 for _ in range(times):
8 original_init(self, *args, **kwargs)
9
10 cls.__init__ = new_init
11 return cls
12
13 return decorator
14
15@repeat_method(3)
16class Counter:
17 count = 0
18
19 def __init__(self):
20 Counter.count += 1
21
22c = Counter()
23print(Counter.count) # 3實用範例:@singleton
1def singleton(cls):
2 """單例模式裝飾器"""
3 instances = {}
4
5 def get_instance(*args, **kwargs):
6 if cls not in instances:
7 instances[cls] = cls(*args, **kwargs)
8 return instances[cls]
9
10 return get_instance
11
12@singleton
13class Database:
14 def __init__(self, host):
15 self.host = host
16 print(f"連接到 {host}")
17
18db1 = Database("localhost") # 連接到 localhost
19db2 = Database("remote") # 不會再次連接
20print(db1 is db2) # True注意:這個實現返回的是函式而不是類別,所以 isinstance(db1, Database) 會失敗。更好的實現:
1def singleton(cls):
2 """保持類別特性的單例裝飾器"""
3 _instance = None
4
5 class SingletonWrapper(cls):
6 def __new__(wrapper_cls, *args, **kwargs):
7 nonlocal _instance
8 if _instance is None:
9 _instance = super().__new__(wrapper_cls)
10 return _instance
11
12 SingletonWrapper.__name__ = cls.__name__
13 SingletonWrapper.__qualname__ = cls.__qualname__
14 return SingletonWrapper【實作層】實用類別裝飾器
@auto_repr
1def auto_repr(cls):
2 """自動生成 __repr__ 方法"""
3 def __repr__(self):
4 attrs = ', '.join(
5 f"{k}={v!r}"
6 for k, v in self.__dict__.items()
7 if not k.startswith('_')
8 )
9 return f"{cls.__name__}({attrs})"
10
11 cls.__repr__ = __repr__
12 return cls
13
14@auto_repr
15class Person:
16 def __init__(self, name, age):
17 self.name = name
18 self.age = age
19
20p = Person("Alice", 30)
21print(p) # Person(name='Alice', age=30)@dataclass 的簡化實現
1def dataclass(cls):
2 """簡化版 dataclass"""
3 # 收集類別屬性中的型別註解
4 annotations = getattr(cls, '__annotations__', {})
5
6 # 生成 __init__
7 def __init__(self, **kwargs):
8 for name in annotations:
9 setattr(self, name, kwargs.get(name))
10
11 # 生成 __repr__
12 def __repr__(self):
13 attrs = ', '.join(
14 f"{name}={getattr(self, name)!r}"
15 for name in annotations
16 )
17 return f"{cls.__name__}({attrs})"
18
19 # 生成 __eq__
20 def __eq__(self, other):
21 if not isinstance(other, cls):
22 return NotImplemented
23 return all(
24 getattr(self, name) == getattr(other, name)
25 for name in annotations
26 )
27
28 cls.__init__ = __init__
29 cls.__repr__ = __repr__
30 cls.__eq__ = __eq__
31
32 return cls
33
34@dataclass
35class Point:
36 x: int
37 y: int
38
39p1 = Point(x=1, y=2)
40p2 = Point(x=1, y=2)
41print(p1) # Point(x=1, y=2)
42print(p1 == p2) # True@timer(方法計時)
1import time
2from functools import wraps
3
4def timer(cls):
5 """為所有公開方法添加計時"""
6 for name, method in list(cls.__dict__.items()):
7 if callable(method) and not name.startswith('_'):
8 @wraps(method)
9 def timed_method(self, *args, _method=method, _name=name, **kwargs):
10 start = time.perf_counter()
11 result = _method(self, *args, **kwargs)
12 elapsed = time.perf_counter() - start
13 print(f"{_name}: {elapsed:.4f}s")
14 return result
15
16 setattr(cls, name, timed_method)
17
18 return cls
19
20@timer
21class Calculator:
22 def slow_add(self, a, b):
23 time.sleep(0.1)
24 return a + b
25
26calc = Calculator()
27calc.slow_add(1, 2) # slow_add: 0.1001s【設計層】動態建立類別
使用 type()
1# 基本用法
2def greet(self):
3 return f"Hello, {self.name}"
4
5Person = type('Person', (), {
6 'species': 'Human',
7 'greet': greet,
8})
9
10p = Person()
11p.name = "Alice"
12print(p.greet()) # Hello, Alice動態繼承
1def create_model(name, fields):
2 """動態建立模型類別"""
3
4 def __init__(self, **kwargs):
5 for field in fields:
6 setattr(self, field, kwargs.get(field))
7
8 def __repr__(self):
9 attrs = ', '.join(f"{f}={getattr(self, f)!r}" for f in fields)
10 return f"{name}({attrs})"
11
12 return type(name, (), {
13 '__init__': __init__,
14 '__repr__': __repr__,
15 '_fields': fields,
16 })
17
18User = create_model('User', ['id', 'name', 'email'])
19Product = create_model('Product', ['id', 'name', 'price'])
20
21u = User(id=1, name="Alice", email="alice@example.com")
22print(u) # User(id=1, name='Alice', email='alice@example.com')條件繼承
1def create_handler(use_async=False):
2 """根據條件選擇基類"""
3
4 if use_async:
5 import asyncio
6
7 class AsyncBase:
8 async def handle(self, data):
9 await asyncio.sleep(0.1)
10 return f"Async: {data}"
11
12 base = AsyncBase
13 else:
14 class SyncBase:
15 def handle(self, data):
16 return f"Sync: {data}"
17
18 base = SyncBase
19
20 return type('Handler', (base,), {})
21
22SyncHandler = create_handler(use_async=False)
23AsyncHandler = create_handler(use_async=True)【選擇指南】
Metaclass vs 類別裝飾器 vs init_subclass
| 特性 | 類別裝飾器 | init_subclass | Metaclass |
|---|---|---|---|
| 學習曲線 | 低 | 低 | 高 |
| 影響子類別 | 否 | 是 | 是 |
| 可堆疊 | 是 | 是(需 super) | 困難 |
| 控制程度 | 類別建立後 | 子類別定義時 | 類別建立中 |
| 適用場景 | 添加/修改方法 | 註冊/驗證 | 深度自訂 |
決策流程
1需要修改類別的行為?
2│
3├── 只在單一類別上?
4│ └── 類別裝飾器
5│
6├── 需要自動影響所有子類別?
7│ ├── 只是註冊或簡單驗證?
8│ │ └── __init_subclass__
9│ │
10│ └── 需要修改類別建立過程?
11│ └── Metaclass
12│
13└── 需要完全動態建立類別?
14 └── type()【實戰】組合使用
1from functools import wraps
2
3def validate_types(cls):
4 """型別驗證裝飾器"""
5 original_init = cls.__init__
6 annotations = getattr(cls, '__annotations__', {})
7
8 @wraps(original_init)
9 def validated_init(self, *args, **kwargs):
10 # 將位置參數轉換為關鍵字參數
11 import inspect
12 sig = inspect.signature(original_init)
13 params = list(sig.parameters.keys())[1:] # 跳過 self
14
15 for i, arg in enumerate(args):
16 if i < len(params):
17 kwargs[params[i]] = arg
18
19 # 驗證型別
20 for name, expected_type in annotations.items():
21 if name in kwargs:
22 value = kwargs[name]
23 if not isinstance(value, expected_type):
24 raise TypeError(
25 f"{name} 應為 {expected_type.__name__},"
26 f"但得到 {type(value).__name__}"
27 )
28
29 original_init(self, **kwargs)
30
31 cls.__init__ = validated_init
32 return cls
33
34@validate_types
35class User:
36 name: str
37 age: int
38
39 def __init__(self, name, age):
40 self.name = name
41 self.age = age
42
43User("Alice", 30) # OK
44# User("Bob", "thirty") # TypeError!思考題
- 類別裝飾器和 Metaclass 可以同時使用嗎?執行順序是什麼?
@dataclass實際上使用了哪些技術?- 動態建立的類別和靜態定義的類別有什麼差異?
實作練習
- 實作一個
@frozen裝飾器,讓類別的實例不可變 - 實作一個
@trace裝飾器,追蹤所有方法呼叫 - 使用
type()建立一個簡單的 Enum 類別
延伸閱讀
上一章:Metaclass 設計與應用 下一章:反射與 inspect 模組