3.5.1 泛型進階
3.5.1 泛型進階
入門系列介紹了 TypeVar 的基本用法。本章深入探討泛型的進階特性,讓你能夠建立型別安全的抽象層。
先備知識
TypeVar 進階
bound 參數
bound 限制 TypeVar 必須是某個型別的子型別:
1from typing import TypeVar
2
3class Animal:
4 def speak(self) -> str:
5 return "..."
6
7class Dog(Animal):
8 def speak(self) -> str:
9 return "Woof!"
10
11class Cat(Animal):
12 def speak(self) -> str:
13 return "Meow!"
14
15# T 必須是 Animal 或其子類別
16T = TypeVar("T", bound=Animal)
17
18def make_speak(animal: T) -> str:
19 return animal.speak() # 型別檢查器知道 animal 有 speak() 方法
20
21# 正確使用
22make_speak(Dog()) # OK
23make_speak(Cat()) # OK
24
25# 錯誤使用
26make_speak("not an animal") # 型別錯誤bound vs 限制型別
1from typing import TypeVar
2
3# 方式一:bound - T 可以是 Animal 或任何子類別
4T_bound = TypeVar("T_bound", bound=Animal)
5
6# 方式二:限制型別 - T 只能是 Dog 或 Cat,不能是其他 Animal 子類別
7T_constrained = TypeVar("T_constrained", Dog, Cat)差異:
| 方式 | 適用情況 |
|---|---|
bound=Animal | T 可以是 Animal 或任何子類別(包括未來新增的) |
TypeVar("T", Dog, Cat) | T 只能是明確列出的型別 |
covariant 與 contravariant
這是泛型最難理解的概念,但對於設計型別安全的 API 非常重要。
問題情境
1class Animal:
2 pass
3
4class Dog(Animal):
5 pass
6
7# 問題:List[Dog] 是 List[Animal] 的子型別嗎?
8def process_animals(animals: list[Animal]) -> None:
9 animals.append(Animal()) # 如果傳入 list[Dog],這會破壞型別安全
10
11dogs: list[Dog] = [Dog(), Dog()]
12process_animals(dogs) # 如果允許,dogs 裡面會有 Animal!Python 的 list 是不變的(invariant),所以 list[Dog] 不是 list[Animal] 的子型別。
covariant(協變)
只讀的容器可以是協變的:
1from typing import TypeVar, Generic, Iterator
2
3T_co = TypeVar("T_co", covariant=True)
4
5class ReadOnlyBox(Generic[T_co]):
6 """只能讀取,不能修改"""
7 def __init__(self, value: T_co) -> None:
8 self._value = value
9
10 def get(self) -> T_co:
11 return self._value
12
13# ReadOnlyBox[Dog] 是 ReadOnlyBox[Animal] 的子型別
14def show_animal(box: ReadOnlyBox[Animal]) -> None:
15 print(box.get())
16
17dog_box: ReadOnlyBox[Dog] = ReadOnlyBox(Dog())
18show_animal(dog_box) # OK - Dog 是 Animal記憶方式:協變 = 輸出方向,子型別可以替代父型別。
contravariant(逆變)
只寫的容器可以是逆變的:
1from typing import TypeVar, Generic
2
3T_contra = TypeVar("T_contra", contravariant=True)
4
5class Handler(Generic[T_contra]):
6 """處理器:只接收值"""
7 def handle(self, value: T_contra) -> None:
8 print(f"Handling: {value}")
9
10# Handler[Animal] 是 Handler[Dog] 的子型別!(反直覺)
11def setup_dog_handler(handler: Handler[Dog]) -> None:
12 handler.handle(Dog())
13
14animal_handler: Handler[Animal] = Handler()
15setup_dog_handler(animal_handler) # OK - 能處理 Animal 就能處理 Dog記憶方式:逆變 = 輸入方向,父型別可以替代子型別。
實際應用
1from typing import Callable
2
3# Callable 的參數是逆變的,返回值是協變的
4# Callable[[Animal], Dog] 是 Callable[[Dog], Animal] 的子型別
5
6def process(func: Callable[[Dog], Animal]) -> None:
7 result = func(Dog())
8 print(result)
9
10def any_animal_to_dog(animal: Animal) -> Dog:
11 return Dog()
12
13process(any_animal_to_dog) # OKGeneric 類別
建立自己的泛型容器
1from typing import TypeVar, Generic, Optional
2
3T = TypeVar("T")
4
5class Stack(Generic[T]):
6 """型別安全的堆疊"""
7
8 def __init__(self) -> None:
9 self._items: list[T] = []
10
11 def push(self, item: T) -> None:
12 self._items.append(item)
13
14 def pop(self) -> Optional[T]:
15 if self._items:
16 return self._items.pop()
17 return None
18
19 def peek(self) -> Optional[T]:
20 if self._items:
21 return self._items[-1]
22 return None
23
24# 使用
25int_stack: Stack[int] = Stack()
26int_stack.push(1)
27int_stack.push(2)
28int_stack.push("three") # 型別錯誤!
29
30str_stack: Stack[str] = Stack()
31str_stack.push("hello")多型別參數
1from typing import TypeVar, Generic
2
3K = TypeVar("K")
4V = TypeVar("V")
5
6class Pair(Generic[K, V]):
7 """鍵值對"""
8
9 def __init__(self, key: K, value: V) -> None:
10 self.key = key
11 self.value = value
12
13 def swap(self) -> "Pair[V, K]":
14 return Pair(self.value, self.key)
15
16# 使用
17pair: Pair[str, int] = Pair("age", 25)
18swapped: Pair[int, str] = pair.swap()繼承泛型類別
1from typing import TypeVar, Generic
2
3T = TypeVar("T")
4
5class Container(Generic[T]):
6 def __init__(self, value: T) -> None:
7 self.value = value
8
9# 方式一:保持泛型
10class Box(Container[T]):
11 def unwrap(self) -> T:
12 return self.value
13
14# 方式二:具體化型別
15class StringBox(Container[str]):
16 def upper(self) -> str:
17 return self.value.upper()Protocol 與結構化子型別
什麼是 Protocol?
Protocol 定義「介面」,任何實現該介面的類別都被視為符合該 Protocol,無需明確繼承。
1from typing import Protocol
2
3class Drawable(Protocol):
4 def draw(self) -> str:
5 ...
6
7# 這個類別沒有繼承 Drawable,但符合 Protocol
8class Circle:
9 def draw(self) -> str:
10 return "○"
11
12class Square:
13 def draw(self) -> str:
14 return "□"
15
16def render(shape: Drawable) -> None:
17 print(shape.draw())
18
19render(Circle()) # OK - Circle 有 draw() 方法
20render(Square()) # OK - Square 有 draw() 方法Protocol vs ABC
| 特性 | Protocol | ABC |
|---|---|---|
| 繼承要求 | 不需要 | 需要 |
| 型別檢查 | 結構化(duck typing) | 名義上(nominal) |
| 執行期檢查 | 需要 runtime_checkable | 內建 |
| 適用場景 | 第三方類別、鬆散耦合 | 自己控制的類別層級 |
1from abc import ABC, abstractmethod
2from typing import Protocol, runtime_checkable
3
4# ABC 方式
5class DrawableABC(ABC):
6 @abstractmethod
7 def draw(self) -> str:
8 ...
9
10class CircleABC(DrawableABC): # 必須繼承
11 def draw(self) -> str:
12 return "○"
13
14# Protocol 方式
15@runtime_checkable
16class DrawableProtocol(Protocol):
17 def draw(self) -> str:
18 ...
19
20class CircleProtocol: # 不需要繼承
21 def draw(self) -> str:
22 return "○"
23
24# 執行期檢查
25print(isinstance(CircleProtocol(), DrawableProtocol)) # True何時選擇 Protocol?
1# 使用 Protocol 的情況:
2
3# 1. 處理第三方類別
4class JSONSerializable(Protocol):
5 def to_json(self) -> str:
6 ...
7
8# 第三方類別可能已經有 to_json(),不需要修改它們
9
10# 2. 定義回調介面
11class EventHandler(Protocol):
12 def on_event(self, event: dict) -> None:
13 ...
14
15# 任何有 on_event 方法的類別或函式都可以使用
16
17# 3. 鬆散耦合
18class Closeable(Protocol):
19 def close(self) -> None:
20 ...
21
22def cleanup(resource: Closeable) -> None:
23 resource.close()
24
25# 檔案、連接、任何有 close() 的東西都可以用帶有屬性的 Protocol
1from typing import Protocol
2
3class Named(Protocol):
4 name: str # 只需要有這個屬性
5
6class Person:
7 def __init__(self, name: str) -> None:
8 self.name = name
9
10class Company:
11 name: str = "Acme Corp"
12
13def greet(entity: Named) -> str:
14 return f"Hello, {entity.name}!"
15
16greet(Person("Alice")) # OK
17greet(Company()) # OK實際範例:型別安全的 Repository 介面
結合以上所有概念,建立一個型別安全的資料存取層:
1from typing import TypeVar, Generic, Protocol, Optional
2from abc import abstractmethod
3
4# 定義實體必須有 id 屬性
5class HasId(Protocol):
6 id: int
7
8# 泛型 Repository 介面
9T = TypeVar("T", bound=HasId)
10
11class Repository(Generic[T]):
12 """資料存取層的抽象介面"""
13
14 @abstractmethod
15 def get(self, id: int) -> Optional[T]:
16 """根據 ID 取得實體"""
17 ...
18
19 @abstractmethod
20 def save(self, entity: T) -> T:
21 """儲存實體"""
22 ...
23
24 @abstractmethod
25 def delete(self, id: int) -> bool:
26 """刪除實體"""
27 ...
28
29 @abstractmethod
30 def find_all(self) -> list[T]:
31 """取得所有實體"""
32 ...
33
34# 具體實現
35class User:
36 def __init__(self, id: int, name: str) -> None:
37 self.id = id
38 self.name = name
39
40class InMemoryUserRepository(Repository[User]):
41 """記憶體中的 User Repository"""
42
43 def __init__(self) -> None:
44 self._storage: dict[int, User] = {}
45 self._next_id = 1
46
47 def get(self, id: int) -> Optional[User]:
48 return self._storage.get(id)
49
50 def save(self, entity: User) -> User:
51 if entity.id == 0:
52 entity.id = self._next_id
53 self._next_id += 1
54 self._storage[entity.id] = entity
55 return entity
56
57 def delete(self, id: int) -> bool:
58 if id in self._storage:
59 del self._storage[id]
60 return True
61 return False
62
63 def find_all(self) -> list[User]:
64 return list(self._storage.values())
65
66# 使用
67def process_user(repo: Repository[User], user_id: int) -> None:
68 user = repo.get(user_id)
69 if user:
70 print(f"Found user: {user.name}")
71
72# 型別安全:不能把 UserRepository 傳給需要 Repository[Product] 的函式常見錯誤
1. 忘記 TypeVar 的名稱參數
1# 錯誤
2T = TypeVar() # TypeError
3
4# 正確
5T = TypeVar("T")2. 在類別方法中重複定義 TypeVar
1T = TypeVar("T")
2
3class Box(Generic[T]):
4 # 錯誤:方法內重新定義 T
5 def transform(self, func: Callable[[T], T]) -> T:
6 T2 = TypeVar("T2") # 這是不同的 TypeVar!
7 ...
8
9 # 正確:直接使用類別的 T
10 def transform(self, func: Callable[[T], T]) -> T:
11 return func(self.value)3. 誤用 covariant/contravariant
1T_co = TypeVar("T_co", covariant=True)
2
3class MutableBox(Generic[T_co]): # 錯誤!
4 def __init__(self, value: T_co) -> None:
5 self._value = value
6
7 def set(self, value: T_co) -> None: # 協變型別不能用在參數位置
8 self._value = value小結
| 概念 | 用途 |
|---|---|
bound | 限制 TypeVar 必須是某型別的子型別 |
covariant | 只讀容器,子型別可替代父型別 |
contravariant | 只寫容器,父型別可替代子型別 |
Generic[T] | 建立泛型類別 |
Protocol | 結構化子型別,不需繼承 |
思考題
- 為什麼
list是不變的而不是協變的? - 什麼情況下應該用 Protocol 而不是 ABC?
- 如何為一個既可讀又可寫的容器設計型別安全的介面?
下一章:3.5.2 異常設計架構
#python #python-advanced #design-patterns #generics #type-system