Python泛型(Generics)使用及說(shuō)明
本文檔詳細(xì)介紹了 Python 泛型的用法,并通過(guò)大量代碼示例展示如何在 Python 中使用泛型進(jìn)行類型安全編程。同時(shí),我們也會(huì)對(duì)比 Java 的泛型機(jī)制,幫助你更好地理解兩者的區(qū)別。
1. 為什么需要泛型?
Python 是一門動(dòng)態(tài)語(yǔ)言,但在大型項(xiàng)目中,為了提高代碼的可維護(hù)性和減少 Bug,我們通常會(huì)使用類型提示 (Type Hints)。
泛型允許我們?cè)诙x函數(shù)、類或接口時(shí),不指定具體的數(shù)據(jù)類型,而是在使用時(shí)再指定。
主要好處:
- 類型安全:靜態(tài)類型檢查器(如
mypy)可以在運(yùn)行前發(fā)現(xiàn)類型錯(cuò)誤。 - 代碼復(fù)用:一套邏輯可以應(yīng)用于多種數(shù)據(jù)類型。
- IDE 智能提示:更好的自動(dòng)補(bǔ)全和代碼導(dǎo)航。
2. 基礎(chǔ)概念與語(yǔ)法
2.1 定義類型變量 (TypeVar)
在 Python 中(3.12 之前),泛型的核心是 TypeVar。
必須先定義一個(gè)類型變量對(duì)象,才能在后續(xù)代碼中使用它。
from typing import TypeVar, List
# 定義一個(gè)類型變量 T
# 習(xí)慣上變量名和字符串參數(shù)保持一致
T = TypeVar('T')
2.2 泛型函數(shù)
一個(gè)簡(jiǎn)單的例子:實(shí)現(xiàn)一個(gè)函數(shù),返回列表中的第一個(gè)元素。
from typing import TypeVar, List
T = TypeVar('T')
def get_first(items: List[T]) -> T:
"""返回列表的第一個(gè)元素,類型與列表元素類型一致"""
return items[0]
# 使用示例
n: int = get_first([1, 2, 3]) # T 被推斷為 int
s: str = get_first(["a", "b"]) # T 被推斷為 str
# IDE 會(huì)報(bào)錯(cuò)的例子:
# x: str = get_first([1, 2, 3]) # 錯(cuò)誤: 期望返回 int,但標(biāo)記為 str
2.3 泛型類
使用 Generic[T] 基類來(lái)定義泛型類。
from typing import TypeVar, Generic
T = TypeVar('T')
class Stack(Generic[T]):
def __init__(self) -> None:
self.items: List[T] = []
def push(self, item: T) -> None:
self.items.append(item)
def pop(self) -> T:
return self.items.pop()
# 具體化使用
int_stack = Stack[int]()
int_stack.push(1)
# int_stack.push("a") # 類型檢查錯(cuò)誤: 期望 int
str_stack = Stack[str]()
str_stack.push("hello")
2.4 多個(gè)類型變量
類似于 Java 的 Map<K, V>。
K = TypeVar('K')
V = TypeVar('V')
class KeyValuePair(Generic[K, V]):
def __init__(self, key: K, value: V):
self.key = key
self.value = value
pair = KeyValuePair[str, int]("age", 25)
2.5 上界約束 (Bound)
有時(shí)我們需要限制 T 必須是某個(gè)類的子類。
class Animal:
def speak(self): pass
class Dog(Animal): ...
class Cat(Animal): ...
# T 必須是 Animal 或其子類
A = TypeVar('A', bound=Animal)
def make_noise(animal: A) -> None:
animal.speak()
make_noise(Dog()) # OK
# make_noise("hello") # Error: str 不是 Animal 的子類
3. Python vs Java 泛型對(duì)比
這是最關(guān)鍵的部分,理解兩者的差異有助于你從 Java 思維轉(zhuǎn)換到 Python 思維。
3.1 語(yǔ)法對(duì)比
| 特性 | Java | Python (3.5 - 3.11) | Python (3.12+) |
|---|---|---|---|
| 定義泛型類 | class Box<T> { ... } | class Box(Generic[T]): ... | class Box[T]: ... |
| 定義泛型方法 | public <T> T func(T x) | def func(x: T) -> T: | def func[T](x: T) -> T: |
| 類型變量聲明 | 隱式聲明 (直接寫 <T>) | 必須顯式聲明 (T = TypeVar('T')) | 隱式聲明 (3.12+ 新語(yǔ)法) |
| 實(shí)例化 | new Box<Integer>() | Box[int]() | Box[int]() |
| 通配符 | List<?> | List[Any] | List[Any] |
| 上界約束 | <T extends Number> | TypeVar('T', bound=Number) | class Box[T: Number]: |
3.2 核心機(jī)制差異
Java: 偽泛型與類型擦除 (Type Erasure)
- 機(jī)制:Java 編譯器在編譯時(shí)檢查類型,但在生成的字節(jié)碼中,所有的
T都會(huì)被替換成Object(或其他上界)。運(yùn)行時(shí) JVM 不知道List<String>和List<Integer>的區(qū)別。 - 后果:你不能在運(yùn)行時(shí)做
if (obj instanceof T)這樣的檢查。
Python: 運(yùn)行時(shí)對(duì)象與靜態(tài)檢查
- 機(jī)制:Python 是動(dòng)態(tài)的。
Generic[T]和TypeVar('T')都是運(yùn)行時(shí)的真實(shí)對(duì)象。 - 檢查:Python 解釋器本身完全忽略這些類型提示,不會(huì)在運(yùn)行時(shí)報(bào)錯(cuò)(除非代碼邏輯本身錯(cuò)了)。類型檢查完全依賴外部工具(如
mypy,pyright, 或 IDE)。 - 后果:你可以運(yùn)行
x: int = "hello",Python 解釋器照樣執(zhí)行不誤。必須配合mypy使用才有意義。
3.3 代碼直接對(duì)比
Java:
// Java 不需要提前定義 T
public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
// 使用
Box<String> box = new Box<>();
box.set("hello");
Python:
from typing import TypeVar, Generic
# Python 必須先定義 T
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self) -> None:
self.content: T = None
def set(self, content: T) -> None:
self.content = content
def get(self) -> T:
return self.content
# 使用
box = Box[str]()
box.set("hello")
3.4 上界約束對(duì)比 (Upper Bound)
Java 使用 extends 關(guān)鍵字來(lái)實(shí)現(xiàn)上界約束,而 Python 在 TypeVar 定義中使用 bound 參數(shù)。
Java:
// T 必須是 Animal 或其子類
public class Zoo<T extends Animal> {
private T animal;
public void set(T animal) {
// 可以安全調(diào)用 Animal 的方法
animal.speak();
}
}
Python:
# T 必須是 Animal 或其子類
T = TypeVar('T', bound='Animal')
class Zoo(Generic[T]):
def __init__(self, animal: T):
self.animal = animal
def set(self, animal: T) -> None:
# 可以安全調(diào)用 Animal 的方法
self.animal.speak()
4. 進(jìn)階用法示例 (結(jié)合你的項(xiàng)目)
在 RAG 系統(tǒng)或數(shù)據(jù)處理管道中,泛型非常有用。
4.1 泛型 Repository 模式
from typing import TypeVar, Generic, List, Optional
from dataclasses import dataclass
# 假設(shè)有兩個(gè)實(shí)體模型
@dataclass
class User:
id: int
name: str
@dataclass
class Document:
id: int
content: str
# 定義泛型 T,約束為必須有 id 屬性 (這里用 Protocol 更高級(jí),但簡(jiǎn)化演示用)
T = TypeVar('T')
class BaseRepository(Generic[T]):
def __init__(self):
self.db: dict[int, T] = {}
def save(self, entity: T) -> None:
# 假設(shè)實(shí)體都有 id 屬性
self.db[entity.id] = entity
def get(self, id: int) -> Optional[T]:
return self.db.get(id)
def find_all(self) -> List[T]:
return list(self.db.values())
# 具體實(shí)現(xiàn)
class UserRepository(BaseRepository[User]):
def find_by_name(self, name: str) -> Optional[User]:
for user in self.db.values():
if user.name == name:
return user
return None
# 使用
user_repo = UserRepository()
user_repo.save(User(1, "Alice"))
user = user_repo.get(1) # 類型自動(dòng)推斷為 User
4.2 泛型 Protocol (類似 Java Interface)
如果你想定義一個(gè)“只要有 read() 方法的對(duì)象”,不管它繼承自誰(shuí)。
from typing import Protocol, TypeVar
T = TypeVar('T')
class Reader(Protocol[T]):
def read(self) -> T:
...
def process_data(reader: Reader[str]) -> None:
print(reader.read())
class FileReader:
def read(self) -> str:
return "file content"
# FileReader 沒(méi)有繼承 Reader,但符合結(jié)構(gòu),可以通過(guò)檢查
process_data(FileReader())
5. 總結(jié)
- 顯式定義:Python (3.12前) 需要
T = TypeVar('T')。 - 繼承 Generic:類需要繼承
Generic[T]才能成為泛型類。 - 工具檢查:泛型主要服務(wù)于靜態(tài)檢查工具和 IDE,運(yùn)行時(shí)不會(huì)強(qiáng)制校驗(yàn)。
- 靈活性:Python 的泛型系統(tǒng)非常強(qiáng)大,配合
Protocol(結(jié)構(gòu)化類型) 可以實(shí)現(xiàn)比 Java 更靈活的模式。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
python 遞歸相關(guān)知識(shí)總結(jié)
這篇文章主要介紹了python 遞歸相關(guān)知識(shí)總結(jié),幫助大家更好的理解和學(xué)習(xí)使用python,感興趣的朋友可以了解下2021-03-03
Python Opencv實(shí)現(xiàn)圖像輪廓識(shí)別功能
這篇文章主要為大家詳細(xì)介紹了Python Opencv實(shí)現(xiàn)圖像輪廓識(shí)別功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04
用pickle存儲(chǔ)Python的原生對(duì)象方法
下面小編就為大家?guī)?lái)一篇用pickle存儲(chǔ)Python的原生對(duì)象方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04
詳談在flask中使用jsonify和json.dumps的區(qū)別
下面小編就為大家分享一篇詳談在flask中使用jsonify和json.dumps的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
Python之兩種模式的生產(chǎn)者消費(fèi)者模型詳解
今天小編就為大家分享一篇Python之兩種模式的生產(chǎn)者消費(fèi)者模型詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-10-10
Python讀寫Redis數(shù)據(jù)庫(kù)操作示例
Redis是一個(gè)開(kāi)源的非關(guān)系型數(shù)據(jù)庫(kù),它采用C語(yǔ)言編寫,是一個(gè)key-value存儲(chǔ)系統(tǒng),它存儲(chǔ)的value類型很多,包括string(字符串),list(鏈表),set(集合),zset(有序集合),hash(哈希)2014-03-03

