Python 中的泛型(Generics)详解

Python 中的泛型(Generics)是类型系统的一部分,虽然它们在日常脚本编写中可能不常见,但在大型项目、库开发或需要严格类型检查的场景中非常有用。


1. 什么是泛型?

泛型(Generics)允许你定义参数化的类型,即一个类型可以接受其他类型作为参数。例如:

  • List[int] 表示“元素类型为 int 的列表”。
  • Dict[str, float] 表示“键为 str、值为 float 的字典”。

泛型的核心目的是提高代码的类型安全性和可读性,同时支持灵活的静态类型检查。


2. Python 泛型的基础

Python 的泛型是通过 typing 模块实现的(Python 3.5+),主要分为两类:

  1. 容器泛型(如 ListDictSet)。
  2. 自定义泛型(通过 TypeVar 或继承 Generic)。

示例 1:内置容器泛型

from typing import List, Dict

def process_numbers(numbers: List[int]) -> float:
    return sum(numbers) / len(numbers)

data: List[int] = [1, 2, 3]
result = process_numbers(data)  # 类型检查通过

示例 2:类型变量(TypeVar

from typing import TypeVar, Sequence

T = TypeVar('T')  # 声明一个泛型类型变量

def first_element(items: Sequence[T]) -> T:
    return items[0]

# 调用时,T 会自动推断为实际类型
print(first_element([1, 2, 3]))    # T 是 int
print(first_element(["a", "b"]))   # T 是 str

3. 为什么需要泛型?

场景 1:避免重复代码

假设你要写一个函数,既能处理 int 列表也能处理 str 列表:

from typing import Sequence, TypeVar

T = TypeVar('T')

def process(items: Sequence[T]) -> T:
    return items[0]

# 无需为每种类型写单独的函数
process([1, 2, 3])     # 返回 int
process(["a", "b"])    # 返回 str

场景 2:明确容器内容的类型

from typing import Dict

# 明确表示键是 str,值是 int
scores: Dict[str, int] = {"Alice": 90, "Bob": 85}

def update_score(db: Dict[str, int], name: str, value: int) -> None:
    db[name] = value

update_score(scores, "Alice", 95)  # 类型检查通过
update_score(scores, 123, 95)      # 类型检查会报错(键必须是 str)

4. 自定义泛型类

通过继承 Generic,可以创建自己的泛型类:

from typing import Generic, TypeVar, List

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("hello")  # 类型检查会报错(应为 int)

str_stack = Stack[str]()
str_stack.push("world")

5. 泛型的约束

可以用 bound 限制泛型类型的范围:

from typing import TypeVar, Number

N = TypeVar('N', bound=Number)  # 只能是 Number 的子类(如 int, float)

def add(a: N, b: N) -> N:
    return a + b

add(1, 2)      # 正确
add(1.5, 3.2)   # 正确
add("a", "b")   # 类型检查报错

6. 动态类型 vs 静态类型中的泛型

  • 动态类型:Python 运行时不会强制泛型约束(List[int]List[str] 运行时是同一个类)。
  • 静态类型检查:工具如 mypy 会根据泛型类型进行验证。

运行时行为

from typing import List

x: List[int] = [1, 2, 3]
x.append("hello")  # 运行时不会报错,但 mypy 会标记为错误

7. 实际应用场景

  1. API 设计:明确函数参数和返回值的类型关系。

    def parse_response(response: Dict[str, Any]) -> Optional[User]:
        ...
    
  2. 库开发:如 pandasDataFramenumpyndarray 可以用泛型标注。

  3. 团队协作:大型项目中减少类型相关的 bug。


8. 常见问题

Q1:泛型会影响性能吗?

不会。泛型仅在静态类型检查时起作用,运行时会被擦除(类似 Java 的泛型擦除)。

Q2:Python 2 能用泛型吗?

不能。泛型是 Python 3 的特性(需 typing 模块支持)。

Q3:泛型和 Union 有什么区别?

  • Union[int, str] 表示“可以是 intstr”。
  • List[T] 表示“所有元素都是类型 T”。

9. 总结

  • 泛型的作用:让类型系统更灵活,同时保持安全性。
  • 常用工具typing.Listtyping.DictTypeVarGeneric
  • 适用场景:大型项目、库开发、需要静态类型检查时。

可以尝试用 mypy 检查以下代码:

from typing import List

def average(numbers: List[float]) -> float:
    return sum(numbers) / len(numbers)

average([1, 2, "3"])  # mypy 会报错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值