1. Generic 和 TypeVar 是什么? #
Generic(泛型) 和 TypeVar(类型变量) 是 Python typing 模块提供的功能,用于创建可以处理多种类型的通用代码,同时保持类型安全。
1.1 为什么需要泛型? #
想象一下,如果你要写一个函数来获取列表的第一个元素:
# 没有泛型:需要为每种类型写一个函数
def get_first_int(lst: list[int]) -> int:
return lst[0]
def get_first_str(lst: list[str]) -> str:
return lst[0]
def get_first_float(lst: list[float]) -> float:
return lst[0]这样写代码重复,而且类型不明确。使用泛型后:
# 使用泛型:一个函数处理所有类型
from typing import TypeVar, List
T = TypeVar('T') # T 表示"某种类型"
def get_first(lst: List[T]) -> T:
return lst[0]
# 可以用于任何类型
first_int = get_first([1, 2, 3]) # 返回 int
first_str = get_first(["a", "b", "c"]) # 返回 str// TypeScript 泛型写法
function getFirst<T>(lst: T[]): T {
return lst[0];
}
// 可以用于任何类型
const firstInt = getFirst([1, 2, 3]); // number
const firstStr = getFirst(["a", "b", "c"]); // string泛型的好处:
- 代码复用:一个函数/类可以处理多种类型
- 类型安全:类型检查器可以验证类型是否正确
- 代码清晰:明确表达"这个函数可以处理任何类型"
- IDE 支持:更好的自动完成和类型提示
2. 前置知识 #
在学习 Generic 和 TypeVar 之前,你需要了解以下基础知识。
2.1 Python 类型提示基础 #
Python 3.5+ 支持类型提示,用于标注变量、函数参数和返回值的类型。
# 基本类型提示
name: str = "张三" # 变量 name 是字符串类型
age: int = 25 # 变量 age 是整数类型
price: float = 99.9 # 变量 price 是浮点数类型
# 函数类型提示
def add(a: int, b: int) -> int:
# a 和 b 是整数类型,返回值也是整数类型
return a + b
# 列表类型提示
numbers: list[int] = [1, 2, 3] # Python 3.9+
# 或者
from typing import List
numbers: List[int] = [1, 2, 3] # Python 3.9 以下2.2 typing 模块 #
typing 模块提供了更多类型提示工具,如 List、Dict、Optional 等。
# 导入 typing 模块中的类型
from typing import List, Dict, Optional
# List[T] 表示元素类型为 T 的列表
numbers: List[int] = [1, 2, 3]
# Dict[K, V] 表示键类型为 K、值类型为 V 的字典
scores: Dict[str, int] = {"张三": 90, "李四": 85}
# Optional[T] 表示类型 T 或 None
name: Optional[str] = None # name 可以是字符串或 None如果你对类型提示不熟悉,建议先学习 Python 类型提示的基础知识。
3. TypeVar:定义类型变量 #
TypeVar(类型变量) 用于定义一个"占位符"类型,表示"某种类型",但在定义时还不确定具体是什么类型。
3.1 基本用法 #
# 导入 TypeVar
from typing import TypeVar
# 创建类型变量 T
# T 可以是任何类型(int、str、float、自定义类等)
T = TypeVar('T')
# 创建另一个类型变量 S
S = TypeVar('S')说明:
TypeVar('T')创建一个名为T的类型变量T表示"某种类型",可以是任何类型- 类型变量的名字通常使用单个大写字母(如
T、K、V)
3.2 在函数中使用 TypeVar #
# 导入 TypeVar 和 List
from typing import TypeVar, List
# 定义类型变量 T
T = TypeVar('T')
# 定义函数:获取列表的第一个元素
# List[T] 表示元素类型为 T 的列表
# -> T 表示返回值类型也是 T
def first_element(lst: List[T]) -> T:
# 返回列表的第一个元素
# 返回类型与列表元素类型相同
return lst[0]
# 主程序入口
if __name__ == "__main__":
# 使用整数列表
# T 被推断为 int
numbers: List[int] = [1, 2, 3]
result: int = first_element(numbers)
print(f"第一个数字:{result},类型:{type(result).__name__}")
# 使用字符串列表
# T 被推断为 str
strings: List[str] = ["a", "b", "c"]
result2: str = first_element(strings)
print(f"第一个字符串:{result2},类型:{type(result2).__name__}")
# 使用浮点数列表
# T 被推断为 float
floats: List[float] = [1.1, 2.2, 3.3]
result3: float = first_element(floats)
print(f"第一个浮点数:{result3},类型:{type(result3).__name__}")运行结果:
第一个数字:1,类型:int
第一个字符串:a,类型:str
第一个浮点数:1.1,类型:float说明:
- 同一个函数可以处理不同类型的列表
- 类型检查器知道返回值的类型与列表元素类型相同
- 如果传入
List[int],返回值就是int;如果传入List[str],返回值就是str
3.3 类型约束:限制类型变量的范围 #
有时候,你希望类型变量只能是某些特定类型,可以使用类型约束。
# 导入 TypeVar
from typing import TypeVar
# 定义只能接受数字类型的类型变量
# Number 只能是 int 或 float
Number = TypeVar('Number', int, float)
# 定义函数:计算两个数字的和
def add_numbers(a: Number, b: Number) -> Number:
# 返回两个数字的和
return a + b
# 主程序入口
if __name__ == "__main__":
# 使用整数(允许)
result1 = add_numbers(1, 2)
print(f"整数相加:{result1}")
# 使用浮点数(允许)
result2 = add_numbers(1.5, 2.5)
print(f"浮点数相加:{result2}")
# 使用字符串(不允许,类型检查器会报错)
# result3 = add_numbers("1", "2") # 类型错误!说明:
TypeVar('Number', int, float)表示Number只能是int或float- 如果传入其他类型(如
str),类型检查器会报错 - 这样可以确保函数只接受特定类型的参数
4. Generic:创建泛型类 #
Generic(泛型类) 用于创建可以接受类型参数的类,让类可以处理多种类型的数据。
4.1 基本用法 #
# 导入 TypeVar 和 Generic
from typing import TypeVar, Generic
# 定义类型变量 T
T = TypeVar('T')
# 定义泛型类 Box
# Generic[T] 表示这个类接受一个类型参数 T
class Box(Generic[T]):
"""一个可以存放任何类型值的盒子"""
def __init__(self, value: T):
# 初始化方法,接受类型为 T 的值
self.value = value
def get(self) -> T:
# 获取值,返回类型为 T
return self.value
def set(self, new_value: T) -> None:
# 设置值,参数类型为 T
self.value = new_value
# 主程序入口
if __name__ == "__main__":
# 创建存放整数的盒子
# Box[int] 表示这个盒子存放整数
int_box: Box[int] = Box[int](42)
print(f"整数盒子:{int_box.get()}")
# 创建存放字符串的盒子
# Box[str] 表示这个盒子存放字符串
str_box: Box[str] = Box[str]("Hello")
print(f"字符串盒子:{str_box.get()}")
# 创建存放列表的盒子
# Box[list] 表示这个盒子存放列表
list_box: Box[list] = Box[list]([1, 2, 3])
print(f"列表盒子:{list_box.get()}")运行结果:
整数盒子:42
字符串盒子:Hello
列表盒子:[1, 2, 3]说明:
Generic[T]表示这个类接受一个类型参数TBox[int]表示存放整数的盒子Box[str]表示存放字符串的盒子- 类型检查器可以验证类型是否正确
// 定义泛型类 Box
class Box<T> {
// 用于存放任何类型值的盒子
private value: T;
constructor(value: T) {
// 初始化方法,接受类型为 T 的值
this.value = value;
}
get(): T {
// 获取值,返回类型为 T
return this.value;
}
set(newValue: T): void {
// 设置值,参数类型为 T
this.value = newValue;
}
}
// 主程序入口
// 创建存放整数的盒子
// Box<number> 表示这个盒子存放整数
const intBox: Box<number> = new Box<number>(42);
console.log(`整数盒子:${intBox.get()}`);
// 创建存放字符串的盒子
// Box<string> 表示这个盒子存放字符串
const strBox: Box<string> = new Box<string>("Hello");
console.log(`字符串盒子:${strBox.get()}`);
// 创建存放列表的盒子
// Box<number[]> 表示这个盒子存放数字数组
const listBox: Box<number[]> = new Box<number[]>([1, 2, 3]);
console.log(`列表盒子:${listBox.get()}`);4.2 泛型栈示例 #
# 导入 TypeVar、Generic 和 Optional
from typing import TypeVar, Generic, Optional, List
# 定义类型变量 T
T = TypeVar('T')
# 定义泛型栈类
class Stack(Generic[T]):
"""泛型栈:可以存放任何类型的元素"""
def __init__(self) -> None:
# 初始化空栈
# items 是元素类型为 T 的列表
self.items: List[T] = []
def push(self, item: T) -> None:
# 入栈:将元素添加到栈顶
self.items.append(item)
def pop(self) -> Optional[T]:
# 出栈:移除并返回栈顶元素
# 如果栈为空,返回 None
return self.items.pop() if self.items else None
def peek(self) -> Optional[T]:
# 查看栈顶元素(不移除)
# 如果栈为空,返回 None
return self.items[-1] if self.items else None
def is_empty(self) -> bool:
# 检查栈是否为空
return len(self.items) == 0
def size(self) -> int:
# 返回栈的大小
return len(self.items)
# 主程序入口
if __name__ == "__main__":
# 创建整数栈
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
int_stack.push(3)
print(f"整数栈大小:{int_stack.size()}")
print(f"栈顶元素:{int_stack.peek()}")
print(f"出栈:{int_stack.pop()}")
print(f"出栈后大小:{int_stack.size()}")
print("-" * 30)
# 创建字符串栈
str_stack: Stack[str] = Stack()
str_stack.push("hello")
str_stack.push("world")
str_stack.push("python")
print(f"字符串栈大小:{str_stack.size()}")
print(f"栈顶元素:{str_stack.peek()}")
print(f"出栈:{str_stack.pop()}")
print(f"出栈后大小:{str_stack.size()}")运行结果:
整数栈大小:3
栈顶元素:3
出栈:3
出栈后大小:2
------------------------------
字符串栈大小:3
栈顶元素:python
出栈:python
出栈后大小:2说明:
Stack[int]表示存放整数的栈Stack[str]表示存放字符串的栈- 同一个类可以处理不同类型的元素
- 类型检查器可以验证类型是否正确
4.3 多个类型参数 #
一个泛型类可以接受多个类型参数。
# 导入 TypeVar 和 Generic
from typing import TypeVar, Generic
# 定义多个类型变量
K = TypeVar('K') # Key 类型
V = TypeVar('V') # Value 类型
# 定义泛型键值对类
# Generic[K, V] 表示这个类接受两个类型参数:K 和 V
class Pair(Generic[K, V]):
"""键值对:键类型为 K,值类型为 V"""
def __init__(self, key: K, value: V):
# 初始化键值对
self.key = key
self.value = value
def get_key(self) -> K:
# 获取键,返回类型为 K
return self.key
def get_value(self) -> V:
# 获取值,返回类型为 V
return self.value
def __str__(self) -> str:
# 字符串表示
return f"Pair(key={self.key}, value={self.value})"
# 主程序入口
if __name__ == "__main__":
# 创建整数键、字符串值的键值对
# Pair[int, str] 表示键是 int,值是 str
pair1: Pair[int, str] = Pair(1, "one")
print(f"键值对1:{pair1}")
print(f"键类型:{type(pair1.get_key()).__name__}")
print(f"值类型:{type(pair1.get_value()).__name__}")
print("-" * 30)
# 创建字符串键、浮点数值的键值对
# Pair[str, float] 表示键是 str,值是 float
pair2: Pair[str, float] = Pair("pi", 3.14)
print(f"键值对2:{pair2}")
print(f"键类型:{type(pair2.get_key()).__name__}")
print(f"值类型:{type(pair2.get_value()).__name__}")
print("-" * 30)
# 创建字符串键、列表值的键值对
# Pair[str, list] 表示键是 str,值是 list
pair3: Pair[str, list] = Pair("numbers", [1, 2, 3])
print(f"键值对3:{pair3}")
print(f"键类型:{type(pair3.get_key()).__name__}")
print(f"值类型:{type(pair3.get_value()).__name__}")运行结果:
键值对1:Pair(key=1, value=one)
键类型:int
值类型:str
------------------------------
键值对2:Pair(key=pi, value=3.14)
键类型:str
值类型:float
------------------------------
键值对3:Pair(key=numbers, value=[1, 2, 3])
键类型:str
值类型:list说明:
Generic[K, V]表示这个类接受两个类型参数Pair[int, str]表示键是int,值是strPair[str, float]表示键是str,值是float- 可以灵活组合不同的类型
5. 实际应用示例 #
5.1 示例1:API 响应包装器 #
在实际项目中,API 响应通常有统一的结构。使用泛型可以让响应包装器处理不同类型的数据。
# 导入 TypeVar、Generic 和 Optional
from typing import TypeVar, Generic, Optional
# 定义类型变量 T
T = TypeVar('T')
# 定义 API 响应包装器类
class ApiResponse(Generic[T]):
"""API 响应包装器:可以包装任何类型的数据"""
def __init__(
self,
success: bool,
data: Optional[T] = None,
error: Optional[str] = None
):
# 初始化响应
# success: 是否成功
# data: 响应数据(类型为 T)
# error: 错误信息(如果有)
self.success = success
self.data = data
self.error = error
@classmethod
def success_response(cls, data: T) -> 'ApiResponse[T]':
# 创建成功响应
# 返回类型是 ApiResponse[T]
return cls(True, data=data)
@classmethod
def error_response(cls, error: str) -> 'ApiResponse[T]':
# 创建错误响应
return cls(False, error=error)
def __str__(self) -> str:
# 字符串表示
if self.success:
return f"ApiResponse(success=True, data={self.data})"
else:
return f"ApiResponse(success=False, error={self.error})"
# 主程序入口
if __name__ == "__main__":
# 创建用户响应(数据是字典)
user_response: ApiResponse[dict] = ApiResponse.success_response(
{"id": 1, "name": "张三", "email": "zhangsan@example.com"}
)
print(f"用户响应:{user_response}")
# 创建商品列表响应(数据是列表)
product_response: ApiResponse[list] = ApiResponse.success_response(
[{"id": 1, "name": "苹果"}, {"id": 2, "name": "香蕉"}]
)
print(f"商品响应:{product_response}")
# 创建错误响应
error_response: ApiResponse[dict] = ApiResponse.error_response(
"用户不存在"
)
print(f"错误响应:{error_response}")运行结果:
用户响应:ApiResponse(success=True, data={'id': 1, 'name': '张三', 'email': 'zhangsan@example.com'})
商品响应:ApiResponse(success=True, data=[{'id': 1, 'name': '苹果'}, {'id': 2, 'name': '香蕉'}])
错误响应:ApiResponse(success=False, error=用户不存在)说明:
ApiResponse[dict]表示响应数据是字典类型ApiResponse[list]表示响应数据是列表类型- 同一个类可以处理不同类型的数据
- 类型检查器可以验证类型是否正确
5.2 示例2:带约束的泛型类 #
有时候,你希望泛型类只能接受特定类型的参数。
# 导入 TypeVar、Generic 和 List
from typing import TypeVar, Generic, List
# 定义只能接受数字类型的类型变量
# Number 只能是 int、float 或 complex
Number = TypeVar('Number', int, float, complex)
# 定义数字统计类
class Statistics(Generic[Number]):
"""数字统计:只能处理数字类型的数据"""
def __init__(self, data: List[Number]):
# 初始化数据列表
self.data = data
def sum(self) -> Number:
# 计算总和,返回类型为 Number
return sum(self.data)
def mean(self) -> float:
# 计算平均值,返回类型为 float
return sum(self.data) / len(self.data) if self.data else 0.0
def max(self) -> Number:
# 返回最大值
return max(self.data) if self.data else 0
def min(self) -> Number:
# 返回最小值
return min(self.data) if self.data else 0
# 主程序入口
if __name__ == "__main__":
# 使用整数列表(允许)
int_stats: Statistics[int] = Statistics([1, 2, 3, 4, 5])
print(f"整数统计:")
print(f" 总和:{int_stats.sum()}")
print(f" 平均值:{int_stats.mean():.2f}")
print(f" 最大值:{int_stats.max()}")
print(f" 最小值:{int_stats.min()}")
print("-" * 30)
# 使用浮点数列表(允许)
float_stats: Statistics[float] = Statistics([1.5, 2.5, 3.5, 4.5])
print(f"浮点数统计:")
print(f" 总和:{float_stats.sum()}")
print(f" 平均值:{float_stats.mean():.2f}")
print(f" 最大值:{float_stats.max()}")
print(f" 最小值:{float_stats.min()}")
# 使用字符串列表(不允许,类型检查器会报错)
# str_stats: Statistics[str] = Statistics(["a", "b", "c"]) # 类型错误!运行结果:
整数统计:
总和:15
平均值:3.00
最大值:5
最小值:1
------------------------------
浮点数统计:
总和:12.0
平均值:3.00
最大值:4.5
最小值:1.5说明:
Statistics[int]表示处理整数列表Statistics[float]表示处理浮点数列表Statistics[str]不允许(类型检查器会报错)- 类型约束确保类只处理特定类型的数据
5.3 示例3:简单的链表 #
# 导入 TypeVar、Generic 和 Optional
from typing import TypeVar, Generic, Optional
# 定义类型变量 T
T = TypeVar('T')
# 定义链表节点类
class Node(Generic[T]):
"""链表节点:存放类型为 T 的数据"""
def __init__(self, data: T):
# 初始化节点
# data: 节点数据(类型为 T)
# next: 指向下一个节点的指针
self.data = data
self.next: Optional[Node[T]] = None
def __str__(self) -> str:
# 字符串表示
return f"Node(data={self.data})"
# 定义链表类
class LinkedList(Generic[T]):
"""链表:可以存放任何类型的元素"""
def __init__(self):
# 初始化空链表
self.head: Optional[Node[T]] = None
def append(self, data: T) -> None:
# 在链表末尾添加元素
new_node = Node(data)
if self.head is None:
# 如果链表为空,新节点成为头节点
self.head = new_node
else:
# 找到最后一个节点
current = self.head
while current.next is not None:
current = current.next
# 将新节点添加到末尾
current.next = new_node
def display(self) -> None:
# 显示链表内容
if self.head is None:
print("链表为空")
return
# 遍历链表并打印
current = self.head
items = []
while current is not None:
items.append(str(current.data))
current = current.next
print(" -> ".join(items))
# 主程序入口
if __name__ == "__main__":
# 创建整数链表
int_list: LinkedList[int] = LinkedList()
int_list.append(1)
int_list.append(2)
int_list.append(3)
print("整数链表:")
int_list.display()
print("-" * 30)
# 创建字符串链表
str_list: LinkedList[str] = LinkedList()
str_list.append("a")
str_list.append("b")
str_list.append("c")
print("字符串链表:")
str_list.display()运行结果:
整数链表:
1 -> 2 -> 3
------------------------------
字符串链表:
a -> b -> c说明:
LinkedList[int]表示存放整数的链表LinkedList[str]表示存放字符串的链表- 同一个类可以处理不同类型的元素
- 类型检查器可以验证类型是否正确
6. 常见问题 #
6.1 什么时候使用泛型? #
使用泛型的场景:
- 容器类:如栈、队列、链表等,需要存放不同类型的数据
- 工具函数:如获取列表第一个元素、合并两个列表等
- API 响应:统一响应格式,但数据类型不同
- 数据处理:如排序、过滤等,可以处理不同类型的数据
不使用泛型的场景:
- 简单函数:只处理一种特定类型,不需要泛型
- 业务逻辑:特定业务场景,类型固定
6.2 TypeVar 和 Generic 的区别? #
- TypeVar:定义类型变量,用于函数和类的类型参数
- Generic:创建泛型类,让类可以接受类型参数
简单理解:
TypeVar是"占位符",表示"某种类型"Generic是"模板",让类可以处理不同类型
6.3 类型检查器会检查泛型吗? #
是的,类型检查器(如 mypy、pyright)会检查泛型类型。
from typing import TypeVar, List
T = TypeVar('T')
def get_first(lst: List[T]) -> T:
return lst[0]
# 类型检查器会验证类型
numbers: List[int] = [1, 2, 3]
result: int = get_first(numbers) # 正确
strings: List[str] = ["a", "b"]
result2: int = get_first(strings) # 错误:result2 应该是 str,不是 int6.4 泛型会影响运行时性能吗? #
不会。泛型只是类型提示,在运行时会被忽略,不会影响性能。
# 运行时,这两段代码是等价的
def func1(lst: List[int]) -> int:
return lst[0]
def func2(lst: List[T]) -> T:
return lst[0]7. 总结 #
7.1 核心概念回顾 #
TypeVar:定义类型变量,表示"某种类型"
- 可以是任意类型:
T = TypeVar('T') - 可以添加约束:
Number = TypeVar('Number', int, float)
- 可以是任意类型:
Generic:创建泛型类,让类可以接受类型参数
- 单个类型参数:
class Box(Generic[T]) - 多个类型参数:
class Pair(Generic[K, V])
- 单个类型参数:
7.2 使用泛型的好处 #
- 类型安全:在编码时就能发现类型错误
- 代码复用:同一逻辑可以处理不同类型的数据
- 代码清晰:明确表达"这个函数/类可以处理任何类型"
- IDE 支持:更好的自动完成和类型检查