Skip to content

01-类型提示基础

Python 版本要求:3.11+ 本章使用 Python 3.11+ 现代语法,主要改进包括:

  • 使用内置泛型(list[int] 而非 List[int]
  • 使用联合操作符(int | str 而非 Union[int, str]
  • 无需从 typing 导入基础容器类型

掌握类型提示的核心语法,让代码更清晰、更安全。


概念铺垫

类型提示是 Python 3.5+ 引入的渐进式类型系统。它不强制运行时检查,而是通过静态分析工具(mypy、pyright)在编码阶段发现类型错误。类型注解存储在 __annotations__ 属性中,核心语法包括容器类型、联合类型、Callable、类型别名和 Any 逃生舱。


L1 理解层:会用

1. 容器类型深入理解

1.1 问题引入

场景: 你写了一个函数处理列表,但别人调用时传了错误类型的元素。

python
# 没有类型提示,看不出应该传什么
def calculate_average(numbers):
    return sum(numbers) / len(numbers)

# 调用者可能传错误的类型
calculate_average(["a", "b", "c"])  # 运行时错误!

问题: 如何让调用者一眼看出应该传整数列表?


1.2 概念解释

容器类型注解: 指定容器(列表、字典、元组、集合)中元素的类型。

┌─────────────────────────────────────────┐
│          容器类型注解语法                │
├─────────────────────────────────────────┤
│                                         │
│  list[int]      整数列表                │
│  dict[str, int] 字符串到整数的映射      │
│  tuple[int, str] 固定类型的元组         │
│  set[str]       字符串集合              │
│                                         │
│  语法:容器[元素类型]                   │
│                                         │
└─────────────────────────────────────────┘

1.3 最简示例

python
# 整数列表
numbers: list[int] = [1, 2, 3, 4, 5]

# 字典:字符串键,整数值
ages: dict[str, int] = {"张三": 25, "李四": 30}

# 元组:两个浮点数(可用于坐标、区间等)
point: tuple[float, float] = (10.5, 20.3)

# 集合:字符串集合
tags: set[str] = {"python", "tutorial", "beginner"}

1.4 详细说明

列表 list[T]:

list[T] 表示"元素类型为 T 的列表",方括号内指定元素类型。

语法结构:
┌──────────────────────────────────────┐
│  list[元素类型]                       │
│                                      │
│  list[int]   → 整数列表              │
│  list[str]   → 字符串列表            │
│  list[dict]  → 字典列表              │
└──────────────────────────────────────┘

为什么要用?

python
# 没有类型提示:看不出元素类型
numbers = [1, 2, 3]  # 是整数?还是字符串?不清楚

# 有类型提示:一目了然
numbers: list[int] = [1, 2, 3]  # 明确:元素是整数
python
# 定义整数列表
def get_numbers() -> list[int]:
    return [1, 2, 3, 4, 5]

# 使用示例
numbers: list[int] = [1, 2, 3]
numbers.append(4)      # 正确:添加整数
# numbers.append("a")  # 类型检查器会警告

字典 dict[K, V]:

dict[K, V] 表示"键类型为 K、值类型为 V 的字典",需要指定两个类型。

语法结构:
┌──────────────────────────────────────┐
│  dict[键类型, 值类型]                  │
│                                      │
│  dict[str, int]   → 字符串键,整数值  │
│  dict[int, str]   → 整数键,字符串值  │
│  dict[str, dict]  → 字典嵌套字典      │
└──────────────────────────────────────┘

为什么要用?

python
# 没有类型提示:不知道键和值是什么类型
scores = {"张三": 90}  # 键是字符串?值是整数?不清楚

# 有类型提示:清晰明确
scores: dict[str, int] = {"张三": 90}  # 键是字符串,值是整数
python
# 键是字符串,值是整数
def count_words(text: str) -> dict[str, int]:
    """统计单词频率"""
    result: dict[str, int] = {}
    for word in text.split():
        result[word] = result.get(word, 0) + 1
    return result

# 使用
word_counts = count_words("hello world hello")
print(word_counts["hello"])  # 2

元组 tuple[T1, T2, ...]:

tuple[T1, T2, ...] 表示"固定位置有固定类型的元组",每个位置指定一个类型。

语法结构:
┌──────────────────────────────────────┐
│  tuple[位置1类型, 位置2类型, ...]      │
│                                      │
│  tuple[int, str]       → (整数, 字符串) │
│  tuple[float, float]   → (浮点数, 浮点数) │
│  tuple[int, ...]       → 可变长度元组   │
│                                      │
│  常见用途示例:                        │
│  • tuple[float, float] → 坐标点、区间 │
│  • tuple[str, int]     → (姓名, 年龄) │
│  • tuple[int, str, bool] → 记录三元组 │
└──────────────────────────────────────┘

为什么要用?

python
# 没有类型提示:不知道每个位置是什么类型
point = (10.5, 20.3)  # 第一个是什么?第二个是什么?

# 有类型提示:位置和类型都清晰
point: tuple[float, float] = (10.5, 20.3)  # 可用作坐标、区间等
user: tuple[str, int] = ("张三", 25)       # (姓名, 年龄)
python
# 固定长度和类型
def get_user() -> tuple[str, int]:
    """返回用户名和年龄"""
    return ("张三", 25)

# 解包使用
name, age = get_user()
print(f"{name} 今年 {age} 岁")

# 可变长度元组
def get_scores() -> tuple[int, ...]:
    """返回任意数量的分数"""
    return (90, 85, 78, 92)

集合 set[T]:

set[T] 表示"元素类型为 T 的集合",方括号内指定元素类型。

语法结构:
┌──────────────────────────────────────┐
│  set[元素类型]                        │
│                                      │
│  set[int]   → 整数集合               │
│  set[str]   → 字符串集合             │
│  set[float] → 浮点数集合             │
└──────────────────────────────────────┘

为什么要用?

python
# 没有类型提示:看不出元素类型
tags = {"python", "java"}  # 是字符串?还是其他?

# 有类型提示:一目了然
tags: set[str] = {"python", "java"}  # 明确:元素是字符串
python
# 字符串集合
def unique_items(items: list[str]) -> set[str]:
    """获取唯一元素"""
    return set(items)

# 使用
unique: set[str] = {"apple", "banana"}
unique.add("orange")     # 正确
# unique.add(123)        # 类型检查器会警告

1.5 渐进复杂

嵌套容器:

python
# 二维列表(矩阵)
matrix: list[list[int]] = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

# 复杂字典
users: dict[str, dict[str, int]] = {
    "张三": {"数学": 90, "英语": 85},
    "李四": {"数学": 78, "英语": 92}
}

# 元组中的列表
data: tuple[list[int], str] = ([1, 2, 3], "示例")

1.6 实际应用

统计单词频率:

python
def analyze_text(text: str) -> dict[str, int]:
    """分析文本,统计单词频率"""
    words: list[str] = text.lower().split()
    freq: dict[str, int] = {}
    
    for word in words:
        freq[word] = freq.get(word, 0) + 1
    
    return freq

# 使用
result = analyze_text("Python is great Python is fun")
print(result)
# {'python': 2, 'is': 2, 'great': 1, 'fun': 1}

处理配置:

python
def load_config(path: str) -> dict[str, str | int]:
    """加载配置文件"""
    config: dict[str, str | int] = {
        "host": "localhost",
        "port": 8080,
        "debug": "false"
    }
    return config

# 使用
config = load_config("config.txt")
host: str = config["host"]
port: int | str = config["port"]

2. 可选值与联合类型

2.1 问题引入

场景: 函数可能找不到结果,应该返回什么?

python
# 没有类型提示,看不出可能返回 None
def find_user(user_id):
    users = {1: "张三", 2: "李四"}
    return users.get(user_id)  # 可能返回 None!

# 调用者可能忘记处理 None
user = find_user(999)
print(user.upper())  # 运行时错误!None 没有 upper 方法

问题: 如何让调用者知道函数可能返回 None?


2.2 概念解释

可选值(Optional): 表示值可能是某种类型,也可能是 None。

联合类型(Union): 表示值可能是多种类型之一。

┌─────────────────────────────────────────┐
│       可选值与联合类型                   │
├─────────────────────────────────────────┤
│                                         │
│  str | None     字符串或 None           │
│  int | str      整数或字符串             │
│  int | float    整数或浮点数             │
│                                         │
│  Python 3.10+ 新语法:使用 |            │
│  旧语法:Optional[str], Union[int, str] │
│                                         │
└─────────────────────────────────────────┘

2.3 最简示例

python
# 可选值:可能返回字符串或 None
def greet(name: str | None) -> str:
    if name is None:
        return "你好,匿名用户"
    return f"你好,{name}"

# 联合类型:可能是整数或字符串
def parse_input(value: str) -> int | str:
    if value.isdigit():
        return int(value)
    return value

# 使用
print(greet("张三"))    # "你好,张三"
print(greet(None))      # "你好,匿名用户"

print(parse_input("123"))  # 123(整数)
print(parse_input("abc"))  # "abc"(字符串)

2.4 详细说明

可选值的使用场景:

python
# 查找函数:可能找不到
def find_item(items: list[int], target: int) -> int | None:
    """查找元素,找不到返回 None"""
    for item in items:
        if item == target:
            return item
    return None

# 使用时必须检查 None
result = find_item([1, 2, 3], 5)
if result is not None:
    print(f"找到:{result}")
else:
    print("未找到")

联合类型的使用场景:

python
# 处理多种输入类型
def process(value: int | str) -> str:
    """处理整数或字符串"""
    if isinstance(value, int):
        return f"整数:{value * 2}"
    return f"字符串:{value.upper()}"

# 使用
print(process(5))      # "整数:10"
print(process("hello")) # "字符串:HELLO"

新旧语法对比:

python
# Python 3.10+ 新语法(推荐)
def get_name() -> str | None: ...

# Python 3.9 旧语法
from typing import Optional
def get_name() -> Optional[str]: ...

# 两者完全等价,优先使用新语法

2.5 渐进复杂

多重联合:

python
# 多种可能的类型
def parse_value(value: str) -> int | float | bool | str:
    """解析字符串为可能的类型"""
    if value.isdigit():
        return int(value)
    try:
        return float(value)
    except ValueError:
        if value.lower() in ("true", "false"):
            return value.lower() == "true"
        return value

# 类型收窄(Type Narrowing)
result = parse_value("123")
if isinstance(result, int):
    print(f"是整数:{result + 100}")  # 类型检查器知道是 int
elif isinstance(result, float):
    print(f"是浮点数:{result:.2f}")

2.6 实际应用

查找用户:

python
def find_user(user_id: int) -> dict[str, str] | None:
    """查找用户,不存在返回 None"""
    users: dict[int, dict[str, str]] = {
        1: {"name": "张三", "email": "zhangsan@example.com"},
        2: {"name": "李四", "email": "lisi@example.com"}
    }
    return users.get(user_id)

# 安全使用
user = find_user(1)
if user is not None:
    print(f"姓名:{user['name']}")
    print(f"邮箱:{user['email']}")
else:
    print("用户不存在")

user = find_user(999)
if user is None:
    print("未找到用户")

解析输入:

python
def safe_parse(data: str) -> int | float | None:
    """安全解析数值"""
    try:
        if "." in data:
            return float(data)
        return int(data)
    except ValueError:
        return None

# 使用
value = safe_parse("3.14")
if value is not None:
    print(f"数值:{value}")
else:
    print("无法解析")

3. 函数类型 Callable

3.1 问题引入

场景: 你需要一个回调函数,但不知道如何标注它的类型。

python
# 没有类型提示,看不出 func 应该是什么
def apply(func, a, b):
    return func(a, b)

# 调用者可能传错误的函数
apply(len, 5, 3)  # 运行时错误!len 只接受一个参数

问题: 如何标注"接受两个整数参数、返回整数的函数"?


3.2 概念解释

Callable: 表示函数类型,指定参数类型和返回值类型。

┌─────────────────────────────────────────┐
│          Callable 语法                   │
├─────────────────────────────────────────┤
│                                         │
│  Callable[[参数类型], 返回类型]          │
│                                         │
│  示例:                                  │
│  Callable[[int, int], int]              │
│  → 接受两个 int,返回 int               │
│                                         │
│  Callable[[], None]                     │
│  → 无参数,无返回值                      │
│                                         │
│  Callable[..., T]                       │
│  → 接受任意参数,返回 T                  │
│                                         │
└─────────────────────────────────────────┘

... (Ellipsis) 在 Callable 中的含义:

┌─────────────────────────────────────────────────────────────┐
│  Callable[..., T] 详解                                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  语法:Callable[..., 返回类型]                               │
│                                                             │
│  含义:                                                      │
│  • ... 表示"接受任意参数"(不限制参数数量和类型)             │
│  • 返回类型明确指定                                          │
│                                                             │
│  适用场景:                                                   │
│  • 装饰器:不关心被装饰函数的参数签名                        │
│  • 回调函数:参数类型不确定                                  │
│  • 高阶函数:参数类型由调用者决定                            │
│                                                             │
│  对比:                                                      │
│  Callable[[int, int], int]  → 明确两个 int 参数             │
│  Callable[[], None]         → 无参数                        │
│  Callable[..., Any]         → 任意参数,任意返回            │
│  Callable[..., T]           → 任意参数,返回类型 T          │
│                                                             │
│  ⚠️ 注意:使用 ... 会丢失参数类型检查                         │
│  ⚠️ 推荐:装饰器使用 ParamSpec 保留参数类型(见高级类型章节)  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.3 最简示例

python
from typing import Callable

# 接受两个整数,返回整数
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

# 定义符合类型的函数
def add(x: int, y: int) -> int:
    return x + y

def multiply(x: int, y: int) -> int:
    return x * y

# 使用
result = apply(add, 5, 3)       # 8
result = apply(multiply, 5, 3)  # 15

3.4 详细说明

无参数函数:

python
from typing import Callable

# 无参数,无返回值
def on_click(callback: Callable[[], None]) -> None:
    print("按钮被点击")
    callback()

def handle_click() -> None:
    print("处理点击事件")

on_click(handle_click)

单一参数函数:

python
# 接受一个字符串,返回整数
def measure(func: Callable[[str], int], text: str) -> int:
    return func(text)

# 使用
length = measure(len, "hello")  # 5

返回函数的函数:

python
# 返回一个函数
def get_operation(op: str) -> Callable[[int, int], int]:
    if op == "add":
        return lambda x, y: x + y
    elif op == "mul":
        return lambda x, y: x * y
    raise ValueError(f"未知操作:{op}")

# 使用
operation = get_operation("add")
result = operation(5, 3)  # 8

接受任意参数的函数(使用 ...):

python
from typing import Callable, TypeVar

T = TypeVar('T')

# 装饰器:不关心原函数的参数签名
def log_call(func: Callable[..., T]) -> Callable[..., T]:
    """日志装饰器,接受任意参数的函数"""
    def wrapper(*args, **kwargs) -> T:
        print(f"调用 {func.__name__}, 参数: {args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

# 可以装饰任何签名的函数
@log_call
def add(a: int, b: int) -> int:
    return a + b

@log_call
def greet(name: str, greeting: str = "Hello") -> str:
    return f"{greeting}, {name}!"

@log_call
def compute(x: float, y: float, op: str = "add") -> float:
    return x + y if op == "add" else x - y

# 类型安全:返回值类型保留
result1: int = add(1, 2)           # 返回 int
result2: str = greet("Alice")      # 返回 str
result3: float = compute(1.5, 2.5) # 返回 float

为什么装饰器常用 Callable[..., T]

┌─────────────────────────────────────────────────────────────┐
│  装饰器类型标注选择                                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  方案一:Callable[..., T](简单,类型宽松)                  │
│  ─────────────────────────────────────────────              │
│  def decorator(func: Callable[..., T]) -> Callable[..., T]:│
│      ...                                                    │
│                                                             │
│  优点:适用于任何函数签名                                   │
│  缺点:丢失参数类型检查                                     │
│                                                             │
│  方案二:ParamSpec(精确,类型安全)                         │
│  ─────────────────────────────────────────────              │
│  P = ParamSpec('P')                                         │
│  def decorator(func: Callable[P, T]) -> Callable[P, T]:    │
│      ...                                                    │
│                                                             │
│  优点:保留完整的参数类型                                    │
│  缺点:语法稍复杂(见高级类型章节)                          │
│                                                             │
│  选择建议:                                                   │
│  • 快速原型:用 Callable[..., T]                            │
│  • 生产代码:用 ParamSpec                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.5 渐进复杂

匿名函数类型:

python
from typing import Callable

# 变量存储函数
add: Callable[[int, int], int] = lambda x, y: x + y
greet: Callable[[str], str] = lambda name: f"Hello, {name}"

# 作为参数传递
def math_operation(
    a: int,
    b: int,
    operation: Callable[[int, int], int]
) -> int:
    return operation(a, b)

# 使用
result = math_operation(10, 5, lambda x, y: x - y)  # 5

3.6 实际应用

策略模式:

python
from typing import Callable

# 定义策略类型
Strategy = Callable[[list[int]], int]

# 使用策略计算
def calculate(values: list[int], strategy: Strategy) -> int:
    return strategy(values)

# 不同策略
sum_strategy: Strategy = lambda v: sum(v)
max_strategy: Strategy = lambda v: max(v)
avg_strategy: Strategy = lambda v: sum(v) // len(v)

# 使用
numbers = [1, 2, 3, 4, 5]
print(calculate(numbers, sum_strategy))  # 15
print(calculate(numbers, max_strategy))  # 5
print(calculate(numbers, avg_strategy))  # 3

事件处理:

python
from typing import Callable

# 定义事件处理器类型
EventHandler = Callable[[str], None]

# 事件系统
class EventEmitter:
    def __init__(self) -> None:
        self._handlers: list[EventHandler] = []
    
    def subscribe(self, handler: EventHandler) -> None:
        self._handlers.append(handler)
    
    def emit(self, event: str) -> None:
        for handler in self._handlers:
            handler(event)

# 使用
emitter = EventEmitter()

def log_event(event: str) -> None:
    print(f"日志:{event}")

def notify_event(event: str) -> None:
    print(f"通知:{event}")

emitter.subscribe(log_event)
emitter.subscribe(notify_event)

emitter.emit("用户登录")
# 输出:
# 日志:用户登录
# 通知:用户登录

4. 类型别名

4.1 问题引入

场景: 复杂类型写起来太长,代码难以阅读。

python
# 复杂的类型注解
def process(data: dict[str, list[tuple[int, str]]]) -> dict[str, list[tuple[int, str]]]:
    ...

# 另一个函数使用同样的复杂类型
def validate(data: dict[str, list[tuple[int, str]]]) -> bool:
    ...

问题: 如何简化复杂类型的书写?


4.2 概念解释

类型别名: 给类型起一个简短的名字,便于复用和理解。

┌─────────────────────────────────────────┐
│          类型别名                        │
├─────────────────────────────────────────┤
│                                         │
│  语法:别名 = 类型                       │
│                                         │
│  示例:                                  │
│  UserId = int                           │
│  UserList = list[dict]                  │
│                                         │
│  使用:                                  │
│  def get_user(id: UserId) -> UserList:  │
│                                         │
└─────────────────────────────────────────┘

4.3 最简示例

python
# 简单别名
UserId = int
UserName = str

# 使用别名
def get_user(user_id: UserId) -> UserName:
    users: dict[UserId, UserName] = {
        1: "张三",
        2: "李四"
    }
    return users.get(user_id, "未知")

# 变量使用别名
user_id: UserId = 1
user_name: UserName = get_user(user_id)

4.4 详细说明

简单别名:

python
# 基础类型别名
Age = int
Email = str
Score = float

# 使用
def create_student(name: str, age: Age, email: Email) -> dict:
    return {"name": name, "age": age, "email": email}

student = create_student("张三", 18, "test@example.com")

复杂别名:

python
# 容器类型别名
UserList = list[dict[str, str]]
ScoreDict = dict[str, list[float]]
Config = dict[str, str | int | bool]

# 使用
def get_users() -> UserList:
    return [{"name": "张三"}, {"name": "李四"}]

def get_scores() -> ScoreDict:
    return {"数学": [90, 85], "英语": [88, 92]}

def load_config() -> Config:
    return {"host": "localhost", "port": 8080}

4.5 渐进复杂

NewType 与别名的区别:

python
from typing import NewType

# NewType:创建"新"类型(类型检查器会区分)
UserId = NewType('UserId', int)
OrderId = NewType('OrderId', int)

# 类型检查器会报错(防止混淆)
def process_user(id: UserId) -> None: ...
def process_order(id: OrderId) -> None: ...

user_id = UserId(123)
order_id = OrderId(456)

process_user(user_id)  # 正确
# process_user(order_id)  # 类型检查器警告!
# process_order(user_id)  # 类型检查器警告!

# 普通别名:不会区分
UserIdAlias = int
OrderIdAlias = int

# 类型检查器不会警告(两者被视为 int)
def process_user_alias(id: UserIdAlias) -> None: ...
def process_order_alias(id: OrderIdAlias) -> None: ...

process_user_alias(OrderIdAlias(456))  # 类型检查器不警告

4.6 实际应用

业务类型定义:

python
# 定义业务相关类型
UserId = NewType('UserId', int)
PostId = NewType('PostId', int)
CommentId = NewType('CommentId', int)

UserName = str
PostTitle = str
CommentContent = str

Timestamp = float

# 组合使用
PostData = dict[PostId, tuple[PostTitle, UserName, Timestamp]]
CommentData = dict[CommentId, tuple[CommentContent, UserName, Timestamp]]

def create_post(
    title: PostTitle,
    author: UserName,
    timestamp: Timestamp
) -> PostData:
    post_id = PostId(1)
    return {post_id: (title, author, timestamp)}

def add_comment(
    post_id: PostId,
    content: CommentContent,
    author: UserName,
    timestamp: Timestamp
) -> CommentData:
    comment_id = CommentId(1)
    return {comment_id: (content, author, timestamp)}

# 使用
post = create_post("Python 教程", "张三", 1234567890.0)
comment = add_comment(PostId(1), "很好的教程", "李四", 1234567895.0)

5. Any 类型:类型检查的"逃生舱"

5.1 什么是 Any

Anytyping 模块中最特殊的类型。它是一个逃生舱(escape hatch),告诉类型检查器:"这个值可以是任意类型,请不要检查它。"

python
from typing import Any

x: Any = 42          # OK: 整数
x = "hello"          # OK: 字符串
x = [1, 2, 3]        # OK: 列表
x.append(4)          # OK: 类型检查器不报错(即使 x 现在是列表)
x.upper()            # OK: 类型检查器不报错(即使没有 upper 方法)

关键特性Any双向兼容的。

  • 任何类型都可以赋值给 Any
  • Any 也可以赋值给任何类型 ✅
  • 调用 Any 类型的任何方法都不会报错 ✅

5.2 Any vs object:重大区别

这是面试和实战中经常混淆的概念:

特性Anyobject
含义"任意类型,跳过检查""Python 万物基类"
赋值给 Any✅ 任何类型都可赋值✅ 任何类型都可赋值
Any 赋值给具体类型x: int = any_val 不报错x: int = obj_val 报错
调用方法✅ 不调用任何方法都通过检查❌ 只允许调用 object 的方法(如 __str__
类型检查器行为关闭检查严格检查
python
from typing import Any

def with_any(val: Any) -> None:
    result: int = val     # ✅ 类型检查器不检查,直接通过
    val.do_anything()     # ✅ 不会报错

def with_object(val: object) -> None:
    result: int = val     # ❌ mypy 报错: Incompatible types
    val.do_anything()     # ❌ mypy 报错: object has no attribute 'do_anything'

简单理解

  • Any = "别管我了,随便过"(关闭类型检查)
  • object = "我不知道具体是什么类型,但它一定是个 Python 对象"(严格检查)

5.3 何时该用 Any

虽然不推荐滥用,但在以下场景 Any 是合理的:

场景 1:渐进式迁移遗留代码

python
# 老项目逐步添加类型标注
def legacy_process(data: Any) -> Any:
    """尚未重构的老函数,先用 Any 标注避免报错"""
    # ... 一堆没有类型标注的代码 ...
    return result

场景 2:处理未知结构的 JSON

python
import json
from typing import Any

def parse_json(text: str) -> Any:
    """JSON 结构不确定,返回 Any 是合理的"""
    return json.loads(text)

# 调用方需要自己处理类型
data = parse_json('{"users": [{"name": "张三"}]}')
# data 是 Any,后续操作不会报类型错误

场景 3:第三方库无类型标注

python
# 某些第三方库没有 .pyi 类型存根
from some_old_lib import OldClient  # 类型未知

def make_api(client: Any, endpoint: str) -> dict[str, str]:
    """client 类型未知,用 Any 避免 mypy 报错"""
    response = client.get(endpoint)
    return response.json()

5.4 何时不该用 Any(及替代方案)

错误用法问题替代方案
def f(x: Any) -> Any类型检查形同虚设用具体类型 def f(x: int) -> str
items: list[Any]丢失元素类型信息用泛型 items: list[T]
callback: Any丢失函数签名Callable[[int], str]
data: Any(处理字典)无字段提示TypedDictdataclass
python
from typing import Callable, TypeVar

# ❌ 滥用 Any 丢失类型信息
def transform(data: Any, func: Any) -> Any:
    return func(data)

# ✅ 用泛型 + Callable 保持类型安全
T = TypeVar("T")
R = TypeVar("R")

def transform(data: T, func: Callable[[T], R]) -> R:
    return func(data)

# 类型检查器现在能推断:
# transform(42, str) → str
# transform("hello", len) → int

5.5 Any vs # type: ignore

两种方式都可以"跳过检查",但有区别:

python
from typing import Any

# 方式 1: 用 Any 标注(推荐)
value: Any = some_unknown_value
result: int = value  # 类型检查器不检查

# 方式 2: 用 type: ignore 注释
value2 = some_unknown_value
result2: int = value2  # type: ignore  # 强行跳过这一行

# 区别:
# Any   → 告诉类型检查器"这个值可以是任何类型"
# ignore → 告诉类型检查器"我知道这行有问题,别管"
#
# 推荐优先用 Any,ignore 作为最后的逃生手段

5.6 最佳实践总结

┌──────────────────────────────────────────────────────────┐
│                    Any 类型使用原则                       │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  1. 能用具体类型就用具体类型                              │
│  2. 不确定结构时,优先用 TypedDict / dataclass            │
│  3. 需要"通用"时,优先用 TypeVar 泛型                     │
│  4. 确实无法确定类型时(JSON / 遗留代码),用 Any         │
│  5. 用 Any 后尽快细化类型(渐进式标注)                    │
│                                                          │
│  ⚠️ Any 是逃生舱,不是默认选项                            │
│                                                          │
└──────────────────────────────────────────────────────────┘

L2 实践层:用好

类型注解最佳实践

推荐做法

做法原因示例
公共 API 必须添加类型便于他人理解和使用def public_func(x: int) -> str:
复杂函数添加类型减少调用时的理解成本多参数、多返回值函数
使用精确类型而非 Any类型检查器才能发挥作用list[int] 而非 list[Any]
返回值类型必须标注帮助理解函数输出-> list[str] 明确返回列表
私有函数可选添加类型内部实现可简化_helper() 可省略
变量首次赋值时添加类型明确变量用途count: int = 0

反模式:不要这样做

python
# ❌ 使用 Any 丢失类型信息
def process(data: Any) -> Any:
    return data
# 问题:
# 1. 类型检查器无法检查
# 2. IDE 无法提供智能提示
# 3. 失去类型提示的意义

# ✅ 正确做法:使用具体类型
def process(data: dict[str, int]) -> dict[str, int]:
    return data
python
# ❌ 过度注解简单代码
x: int = 1
y: int = 2
z: int = x + y  # 每个变量都注解,冗余

# ✅ 正确做法:让类型检查器推断
x = 1
y = 2
z = x + y  # 类型检查器自动推断为 int
python
# ❌ 省略公共函数的返回值类型
def calculate(data: list[int]):  # 返回什么?
    return sum(data) / len(data)

# ✅ 正确做法:标注返回值
def calculate(data: list[int]) -> float:
    return sum(data) / len(data)
python
# ❌ 类型注解与实际不符
def get_name() -> str:
    return None  # 返回值与声明不符!

# ✅ 正确做法:使用可选类型
def get_name() -> str | None:
    return None  # 正确

何时添加类型

场景是否推荐原因
公共 API(模块导出函数)✅ 必须用户需要理解接口
类的公共方法✅ 必须调用者需要知道参数类型
复杂逻辑函数✅ 推荐帮助理解输入输出
私有辅助函数❓ 可选内部实现,可简化
简单表达式❌ 不推荐类型可推断,冗余
快速原型开发❓ 可选先跑起来再添加类型
脚本文件❓ 可选单次使用,类型价值低

类型注解渐进策略

渐进添加类型路线:
┌─────────────────────────────────────────────────────┐
│  第 1 步:公共 API                                    │
│  ├── 导出的函数和类                                  │
│  └── 关键入口点                                      │
│                                                      │
│  第 2 步:核心模块                                    │
│  ├── 数据处理函数                                    │
│  ├── 业务逻辑类                                      │
│                                                      │
│  第 3 步:辅助代码                                    │
│  ├── 内部工具函数                                    │
│  ├── 私有方法                                        │
│                                                      │
│  第 4 步:全覆盖                                      │
│  ├── 所有变量                                        │
│  ├── 所有表达式                                      │
└─────────────────────────────────────────────────────┘

L3 专家层:深入

Python 如何实现类型提示

类型注解的存储机制

类型提示内部结构:
┌─────────────────────────────────────────────────────┐
│  函数对象                                            │
│                                                      │
│  ┌─────────────────────────────────────┐            │
│  │  __annotations__ 属性                │            │
│  │                                      │            │
│  │  {'参数名': 类型对象,                 │            │
│  │   'return': 返回类型}                │            │
│  └─────────────────────────────────────┘            │
│                                                      │
│  存储时机:                                          │
│  ├── 函数定义时解析注解                              │
│  ├── 存入 __annotations__ 字典                      │
│  ├── 类型对象可以是类或字符串                        │
│                                                      │
│  特点:                                              │
│  ├── 运行时不强制检查                                │
│  ├── 仅供类型检查器使用                              │
│  ├── 可通过 get_type_hints() 获取                   │
└─────────────────────────────────────────────────────┘

演示代码

python
def greet(name: str, age: int) -> str:
    return f"Hello {name}, age {age}"

# 查看 __annotations__ 属性
print(greet.__annotations__)
# {'name': <class 'str'>, 'age': <class 'int'>, 'return': <class 'str'>}

# 直接访问
print(greet.__annotations__['name'])    # <class 'str'>
print(greet.__annotations__['return'])  # <class 'str'>

annotations 属性详解

python
class User:
    name: str
    age: int

    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age

# 类的 __annotations__
print(User.__annotations__)
# {'name': <class 'str'>, 'age': <class 'int'>}

# 模块的 __annotations__
# 在模块级别定义的变量注解
MAX_SIZE: int = 100
print(__annotations__)  # 包含 MAX_SIZE: int

字符串注解(延迟评估)

python
# 前向引用:使用字符串注解
class Node:
    def __init__(self, value: int) -> None:
        self.value = value

    # 此时 Node 类尚未完全定义,用字符串
    def connect(self, other: "Node") -> None:
        pass

# 字符串注解在 __annotations__ 中保持字符串
print(Node.connect.__annotations__)
# {'other': 'Node', 'return': None}

# 使用 get_type_hints 解析字符串
from typing import get_type_hints
print(get_type_hints(Node.connect))
# {'other': <class '__main__.Node'>, 'return': None}

性能考量

操作时间复杂度空间复杂度说明
函数定义时解析注解O(n)O(n)n 为注解数量,存入字典
访问 annotationsO(1)O(1)字典访问
get_type_hints()O(n)O(n)解析所有注解,包括字符串
运行时类型检查O(n)O(n)需额外实现,非内置

性能注意事项:

python
# ❌ 频繁调用 get_type_hints
def validate_on_every_call(func, *args):
    hints = get_type_hints(func)  # 每次调用都解析
    # ...

# ✅ 缓存类型提示
def validate_cached(func, *args):
    # 只解析一次,后续使用缓存
    if not hasattr(func, '_cached_hints'):
        func._cached_hints = get_type_hints(func)
    hints = func._cached_hints
    # ...

设计动机

设计选择原因替代方案对比
类型提示不运行时检查保持 Python 动态特性,不影响性能TypeScript 编译时检查,运行时无类型
annotations 存储注解统一的注解访问方式,便于工具使用Java 使用反射 API
字符串注解支持解决前向引用问题,类定义时可引用自身C++ 使用模板,编译时解析
可选类型提示不强制,渐进式采用Go 强制类型,无动态特性

知识关联

类型提示知识关联:
                    ┌───────────────┐
                    │  类型检查器   │
                    │  mypy/pyright │
                    └───────────────┘


┌───────────────┐     ┌───────────────┐     ┌───────────────┐
│  __annotations__ │────→│  类型提示     │────→│  get_type_hints │
│  属性          │     │  语法         │     │  函数         │
└───────────────┘     └───────────────┘     └───────────────┘


                    ┌───────────────┐
                    │  运行时检查   │
                    │  手动实现     │
                    └───────────────┘


                    ┌───────────────┐
                    │  数据验证库   │
                    │  Pydantic     │
                    └───────────────┘

知识点补充:from __future__ import annotations

PEP 563 延迟注解(Python 3.7+ 引入,Python 3.14 将成为默认行为)

本项目几乎所有 .py 文件首行都有这句导入:

python
from __future__ import annotations

解决了什么问题?

问题 1:前向引用(Forward Reference)—— 类还未定义完成就引用自己

python
# ❌ 没有 __future__ 时,会报错 NameError
class Node:
    def __init__(self, value: int, next: Node = None):  # Node 还未定义完成!
        self.value = value
        self.next = next

# 没有 __future__ 时的 workaround:加引号
class Node:
    def __init__(self, value: int, next: "Node" = None):
        self.value = value
        self.next = next

# ✅ 启用延迟注解后,类型注解变为字符串存储,直接写无需引号
from __future__ import annotations

class Node:
    def __init__(self, value: int, next: Node = None):  # 正常工作
        self.value = value
        self.next = next

问题 2:减少导入开销

python
# ❌ 没有 __future__ 时,模块被导入时必须导入 datetime
from datetime import datetime

class Event:
    def __init__(self, name: str, created_at: datetime) -> None:
        self.name = name
        self.created_at = created_at

# ✅ 启用后,类型注解以字符串存储,不会在导入时求值
# 如果此模块被其他模块导入,datetime 不会被强制导入
from __future__ import annotations

class Event:
    def __init__(self, name: str, created_at: datetime) -> None:
        self.name = name
        self.created_at = created_at

实际效果对比

特性未启用启用 from __future__ import annotations
注解存储方式运行时求值(类型对象)字符串存储(不解析)
前向引用必须加引号 'Node'直接写 Node
导入检查必须提前导入所有类型可延迟导入
Python 3.10+ 联合语法在 3.9 中需 Union[int, str]int | str 可用(需 typing_extensions)

注意事项

python
# ⚠️ 延迟注解的局限:运行时需要类型时,需要 get_type_hints()
from __future__ import annotations
from typing import get_type_hints

class Node:
    def __init__(self, next: Node = None) -> None:
        self.next = next

# 直接访问 __annotations__ 得到字符串
print(Node.__init__.__annotations__)
# → {'next': 'Node', 'return': 'None'}

# 需要解析为类型对象时用 get_type_hints()
hints = get_type_hints(Node.__init__)
print(hints['next'])
# → <class '__main__.Node'>

为什么本项目每个文件都加?

  1. 代码一致 — 所有文件统一风格,避免有的加有的不加
  2. 前向引用 — OOP 代码中类互相引用很常见(如 Library 引用 BookItem
  3. 未来兼容 — Python 3.14+ 将成为默认行为,提前启用避免迁移
  4. 性能优化 — 模块导入更快,类型注解不会拖慢启动时间

与 PEP 649(Python 3.14 默认行为)的关系

Python 最终选择了 PEP 649(延迟求值注解)作为 3.14 的默认行为,而非 PEP 563。两者的区别:

特性PEP 563(当前 __future__PEP 649(Python 3.14+ 默认)
存储方式字符串惰性求值表达式
运行时需要类型get_type_hints()typing.get_annotations() 自动求值
兼容性可能破坏使用 __annotations__ 的代码向后兼容

建议:现在就用 from __future__ import annotations,Python 3.14 迁移时只需删除这行导入。


本章总结(完整版)

核心语法速查

语法说明示例
list[int]整数列表numbers: list[int] = [1, 2, 3]
dict[str, int]字典类型ages: dict[str, int] = {"张三": 25}
tuple[int, str]固定类型元组item: tuple[int, str] = (1, "a")
set[str]字符串集合tags: set[str] = {"a", "b"}
`strNone`可选值
`intstr`联合类型
Callable[[int], str]函数类型func: Callable[[int], str]
UserId = int类型别名id: UserId = 1
Any任意类型(逃生舱)data: Any = json.loads(text)