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 会报错
    

    作者:cugleem

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python泛型(Generics)概念详解及运用

    发表回复