Learn
Python/13-type-hints

类型提示

类型提示(Type Hints)让 Python 兼顾"动态语言的灵活"和"静态语言的可读"。Python 不会在运行时强制检查类型,但 IDE、mypy、pyright 能基于提示给你更准确的自动补全和错误提示。

1. 最基础的注解

变量与函数注解
# 变量注解
name: str = "Alice"
age: int = 30
scores: list[int] = [85, 92, 78]
profile: dict[str, int | str] = {"city": "北京", "zip": "100000"}
 
# 函数注解
def add(a: int, b: int) -> int:
    return a + b
 
def greet(name: str, excited: bool = False) -> str:
    suffix = "!" if excited else "."
    return f"Hi, {name}{suffix}"
 
print(add(3, 4))
print(greet("Alice", excited=True))
 
# Python 3.10+ 可以直接写 int | str
# 老版本需要 from __future__ import annotations
def parse(s: str) -> int | None:
    return int(s) if s.isdigit() else None
 
print(parse("42"), parse("hi"))
ℹ️注解不强制!
def add(a: int, b: int) -> int: ...
add("hi", "world")  # 运行时不会报错!

类型提示本质是给工具看的文档。要严格检查用 mypy / pyright。

2. Optional / Union / Any

Optional 与 Union
from typing import Optional, Union, Any
 
# Optional[X] = Union[X, None] = X | None
def find_user(uid: int) -> Optional[dict]:
    users = {1: {"name": "Alice"}, 2: {"name": "Bob"}}
    return users.get(uid)
 
print(find_user(1))
print(find_user(99))
 
# Union[A, B] = A | B
def stringify(v: Union[int, float, str]) -> str:
    return str(v)
 
print(stringify(1), stringify(3.14), stringify("hi"))
 
# Any: 不做类型检查(动态逃生口)
def debug_print(x: Any) -> None:
    print("debug:", x, type(x).__name__)
 
debug_print(42)
debug_print([1, 2, 3])
debug_print({"k": "v"})
💡优先用 | 而不是 Union

Python 3.10+ 支持 int | str、list[int] | None 这种写法,比 Union[int, str]、Optional[list[int]] 更短。需要兼容老版本就 from __future__ import annotations。

3. dataclass —— 少写一半的样板代码

写"主要用来存数据"的类时,@dataclass 帮你自动生成 __init__、__repr__、__eq__:

@dataclass
from dataclasses import dataclass, field
from typing import List
 
@dataclass
class User:
    name: str
    age: int = 0
    skills: List[str] = field(default_factory=list)
 
    def introduce(self) -> str:
        return f"我是 {self.name}, {self.age} 岁, 技能: {', '.join(self.skills) or '无'}"
 
u1 = User("Alice", 30, ["Python", "Go"])
u2 = User("Bob")
u3 = User("Alice", 30, ["Python", "Go"])
 
print(u1.introduce())
print(u2.introduce())
print("u1 == u3:", u1 == u3)        # 自动 __eq__
print(u1)                            # 自动 __repr__
⚠️可变默认值必须用 default_factory
@dataclass
class Bad:
    items: list[int] = []   # 错!所有实例共享同一个 list

正解:items: list[int] = field(default_factory=list)。

frozen 与 asdict
from dataclasses import dataclass, asdict, astuple
import json
 
@dataclass(frozen=True)        # 实例不可变(哈希可作 dict key)
class Point:
    x: float
    y: float
 
p = Point(3, 4)
print(p, hash(p))
try:
    p.x = 99
except Exception as e:
    print("frozen 阻止修改:", type(e).__name__)
 
# dataclass <-> dict
print(asdict(p))
print(astuple(p))
 
# dataclass 直接喂给 json.dumps
@dataclass
class Product:
    name: str
    price: float
 
prod = Product("笔记本", 12.5)
print(json.dumps(asdict(prod), ensure_ascii=False))

4. TypeVar 与 Generic

当你想写一个"对任意类型都成立"的函数或类(如容器),用 TypeVar:

TypeVar 入门
from typing import TypeVar, Sequence
 
T = TypeVar("T")            # 通用类型变量
 
def first(items: Sequence[T]) -> T | None:
    return items[0] if items else None
 
print(first([1, 2, 3]))            # 推断为 int
print(first(["a", "b", "c"]))      # 推断为 str
print(first([]))                   # None
 
# 约束型 TypeVar
from typing import TypeVar
 
Number = TypeVar("Number", int, float)   # 必须是 int 或 float
 
def double(x: Number) -> Number:
    return x * 2
 
print(double(3), double(1.5))

5. 泛型类

Generic 泛型类
from typing import Generic, TypeVar, Iterator
 
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()
 
    def peek(self) -> T:
        return self._items[-1]
 
    def __len__(self) -> int:
        return len(self._items)
 
    def __iter__(self) -> Iterator[T]:
        return iter(reversed(self._items))
 
# 装 int
s1: Stack[int] = Stack()
s1.push(1); s1.push(2); s1.push(3)
print("s1 pop:", s1.pop(), "len:", len(s1))
 
# 装 str
s2: Stack[str] = Stack()
s2.push("a"); s2.push("b")
print("s2:", list(s2))

🎯 练习

用 @dataclass 实现一个 Book 数据类,包含 title: str、author: str、year: int = 2024。再写一个泛型函数 latest(books: list[T]) -> T | None,返回"year 最大的那本"(用 getattr 或 dataclass 的 dataclasses.astuple 取 year 也行)。处理空列表返回 None。

Book + latest()
from dataclasses import dataclass
 
@dataclass
class Book:
    # 你的代码
    pass
 
# latest 应该返回 year 最大的那本
def latest(books):
    pass
 
 
b1 = Book("三体", "刘慈欣", 2008)
b2 = Book("活着", "余华", 1993)
b3 = Book("百年孤独", "马尔克斯", 1967)
print(latest([b1, b2, b3]))
print(latest([]))
🎯提示

max(books, key=lambda b: b.year, default=None) 一行搞定。

小结

  • ✅ 变量与函数都可以加注解,运行时不强制检查
  • ✅ Optional[X] / X | None、Union[A, B] / A | B、Any 是高频工具
  • ✅ @dataclass 自动生成 __init__ / __repr__ / __eq__;frozen=True 不可变
  • ✅ TypeVar + Generic 让你写类型安全的容器和工具
  • ✅ 配合 mypy / pyright 能在写代码时就发现类型错误

下一章 并发编程:threading、multiprocessing、asyncio —— 写出能榨干多核 CPU / 高 I/O 利用率的程序。