Python 类型提示 typing
目录
学习目标:掌握 Python 类型提示系统,学会使用 typing 模块和现代语法编写类型安全的代码,理解静态类型检查的价值。
1. 为什么需要类型提示
1.1 动态类型的困境
# 没有类型提示:参数和返回值类型不明确
def process(data):
result = []
for item in data:
result.append(item * 2)
return result
# 调用者无法知道:
# - data 应该是什么类型?列表?字典?
# - item 支持乘法吗?
# - 返回什么类型?1.2 类型提示的好处
- 可读性:代码即文档,一目了然
- IDE 支持:智能补全、错误提示
- 静态检查:运行前发现类型错误
- 重构安全:修改接口时自动发现影响范围
- 团队协作:减少沟通成本
2. 基本类型提示
2.1 变量与函数
# 变量类型提示
name: str = "张三"
age: int = 25
height: float = 1.75
is_student: bool = True
# 函数类型提示
def greet(name: str) -> str:
return f"Hello, {name}"
def add(a: int, b: int) -> int:
return a + b
def is_even(n: int) -> bool:
return n % 2 == 0
# 多种返回类型
def parse_int(s: str) -> int | None: # Python 3.10+ 语法
try:
return int(s)
except ValueError:
return None2.2 Python 3.10+ 新语法
# 旧写法(Python 3.9 及以下)
from typing import Optional, Union, List, Dict, Tuple
def old_style(x: Optional[int] = None) -> Union[str, int]:
pass
data: List[int] = [1, 2, 3]
mapping: Dict[str, int] = {"a": 1}
point: Tuple[int, int] = (10, 20)
# 新写法(Python 3.10+)
def new_style(x: int | None = None) -> str | int:
pass
data: list[int] = [1, 2, 3]
mapping: dict[str, int] = {"a": 1}
point: tuple[int, int] = (10, 20)3. 复合类型
3.1 容器类型
from typing import List, Dict, Set, Tuple, FrozenSet
# 列表
names: list[str] = ["Alice", "Bob"]
numbers: list[int | float] = [1, 2, 3.14]
# 字典
config: dict[str, str] = {"host": "localhost", "port": "8080"}
user_ages: dict[str, int] = {"Alice": 25, "Bob": 30}
# 集合
tags: set[str] = {"python", "web", "api"}
# 元组
# 固定长度元组
point: tuple[int, int] = (10, 20)
# 固定结构元组
record: tuple[str, int, bool] = ("Alice", 25, True)
# 可变长度同类型元组
scores: tuple[int, ...] = (90, 85, 92, 88)3.2 嵌套类型
# 嵌套列表
matrix: list[list[int]] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
# 字典的值为列表
groups: dict[str, list[str]] = {
"admins": ["alice", "bob"],
"users": ["charlie", "dave"]
}
# 复杂嵌套
api_response: dict[str, list[dict[str, int | str]]] = {
"users": [
{"id": 1, "name": "Alice", "age": 25},
{"id": 2, "name": "Bob", "age": 30}
]
}4. typing 模块常用类型
4.1 Optional 和 Union
from typing import Optional, Union
# Optional[X] 等价于 X | None
def find_user(user_id: int) -> Optional[dict]:
"""可能返回用户字典或 None"""
users = {1: {"name": "Alice"}}
return users.get(user_id)
# Union 多种类型
def process_value(value: Union[int, str, float]) -> str:
"""处理多种类型的值"""
return str(value)
# Python 3.10+ 可以用 | 语法
def process_value_new(value: int | str | float) -> str:
return str(value)4.2 Any 和 NoReturn
from typing import Any, NoReturn
# Any:任意类型(尽量少用)
def log(message: Any) -> None:
print(f"[LOG] {message}")
# NoReturn:函数永不返回
def fatal_error(message: str) -> NoReturn:
import sys
print(f"Fatal: {message}")
sys.exit(1)4.3 Callable(可调用对象)
from typing import Callable
# Callable[[参数类型...], 返回类型]
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
# 使用
result = apply(lambda x, y: x + y, 3, 5) # 8
# 无参数的 Callable
def run_task(task: Callable[[], None]) -> None:
task()
# 不关心参数的 Callable
from typing import Any
handler: Callable[..., Any] = print4.4 TypeVar(类型变量)
from typing import TypeVar
# 定义类型变量
T = TypeVar('T')
# 泛型函数:输入和输出类型一致
def first(items: list[T]) -> T:
return items[0]
# 使用
names: list[str] = ["Alice", "Bob"]
first_name: str = first(names) # 类型推断为 str
numbers: list[int] = [1, 2, 3]
first_num: int = first(numbers) # 类型推断为 int
# 约束类型变量
Number = TypeVar('Number', int, float)
def add(a: Number, b: Number) -> Number:
return a + b
# add(1, 2) ✓ 返回 int
# add(1.5, 2.5) ✓ 返回 float
# add(1, 2.5) ✗ 类型不匹配5. 泛型(Generics)
5.1 泛型类
from typing import Generic, TypeVar
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:
if not self._items:
raise IndexError("栈为空")
return self._items.pop()
def peek(self) -> 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)
# 使用
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
value: int = int_stack.pop() # 类型安全
str_stack: Stack[str] = Stack()
str_stack.push("hello")
text: str = str_stack.pop()5.2 泛型方法
from typing import TypeVar, K, V
K = TypeVar('K')
V = TypeVar('V')
class BinaryTree(Generic[K, V]):
"""泛型二叉树节点"""
def __init__(self, key: K, value: V) -> None:
self.key = key
self.value = value
self.left: BinaryTree[K, V] | None = None
self.right: BinaryTree[K, V] | None = None
def insert(self, key: K, value: V) -> None:
if key < self.key:
if self.left is None:
self.left = BinaryTree(key, value)
else:
self.left.insert(key, value)
elif key > self.key:
if self.right is None:
self.right = BinaryTree(key, value)
else:
self.right.insert(key, value)6. Protocol(结构化类型)
from typing import Protocol
# 定义协议(类似接口)
class Drawable(Protocol):
def draw(self) -> None:
...
class Serializable(Protocol):
def to_json(self) -> str:
...
# 实现协议(不需要显式继承)
class Circle:
def draw(self) -> None:
print("绘制圆形")
class Square:
def draw(self) -> None:
print("绘制方形")
# 使用协议作为类型
def render(shape: Drawable) -> None:
shape.draw()
# Circle 和 Square 都实现了 draw 方法,自动满足 Drawable 协议
render(Circle()) # ✓
render(Square()) # ✓7. TypedDict(类型化字典)
from typing import TypedDict
# 定义类型化字典
class UserDict(TypedDict):
id: int
name: str
email: str
age: int # 必需字段
# 可选字段(Python 3.11+)
class UserDictOptional(TypedDict, total=False):
id: int
name: str
email: str # 所有字段可选
# 混合必需和可选(Python 3.11+)
class Movie(TypedDict):
title: str
year: int
class MovieWithRating(Movie, total=False):
rating: float
# 使用
user: UserDict = {
"id": 1,
"name": "张三",
"email": "zhangsan@example.com",
"age": 25
}
# 类型检查器会验证字段完整性8. Literal 和 Final
from typing import Literal, Final
# Literal:字面量类型
def set_mode(mode: Literal["dev", "prod", "test"]) -> None:
print(f"设置模式: {mode}")
set_mode("dev") # ✓
set_mode("debug") # ✗ 类型错误
# 常量定义
MAX_SIZE: Final[int] = 100
API_BASE_URL: Final[str] = "https://api.example.com"
# Final 防止重新赋值
class Config:
VERSION: Final[str] = "1.0.0"
# VERSION = "2.0" # 类型检查器会报错9. 类型别名
from typing import TypeAlias
# 简单别名
JSON: TypeAlias = dict[str, 'JSON'] | list['JSON'] | str | int | float | bool | None
# 使用
def parse_json(text: str) -> JSON:
import json
return json.loads(text)
# Python 3.12+ 新语法
# type JSON = dict[str, JSON] | list[JSON] | str | int | float | bool | None
# 复杂类型别名
UserId = int
UserName = str
UserMap = dict[UserId, UserName]
users: UserMap = {1: "Alice", 2: "Bob"}10. 重载(Overload)
from typing import overload
# 重载签名(仅用于类型检查)
@overload
def get_value(key: str) -> str: ...
@overload
def get_value(key: int) -> int: ...
@overload
def get_value(key: None) -> None: ...
# 实际实现
def get_value(key):
"""根据 key 类型返回不同类型"""
if key is None:
return None
elif isinstance(key, str):
return f"string: {key}"
elif isinstance(key, int):
return key * 2
# 类型检查器知道:
result1: str = get_value("hello") # ✓
result2: int = get_value(42) # ✓
result3: None = get_value(None) # ✓11. 静态类型检查
11.1 使用 mypy
# 安装
pip install mypy
# 检查单个文件
mypy script.py
# 检查整个项目
mypy src/
# 严格模式
mypy --strict src/
# 忽略缺失导入
mypy --ignore-missing-imports src/11.2 配置文件
# pyproject.toml
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false11.3 类型错误示例
# mypy 会检测到的错误
def add(a: int, b: int) -> int:
return a + b
add(1, 2) # ✓
add("1", "2") # ✗ error: Argument 1 to "add" has incompatible type "str"
add(1, 2, 3) # ✗ error: Too many arguments
result: str = add(1, 2) # ✗ error: Incompatible types in assignment
# 忽略特定行
result = some_untyped_function() # type: ignore12. Pydantic 与类型提示
# pip install pydantic
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
class User(BaseModel):
"""使用类型提示进行数据验证"""
id: int
name: str = Field(..., min_length=2, max_length=50)
email: EmailStr
age: int = Field(..., ge=0, le=150)
bio: Optional[str] = None
# 自动验证
user = User(
id=1,
name="张三",
email="zhangsan@example.com",
age=25
)
# 类型错误会抛出异常
try:
invalid_user = User(
id="not_an_int", # 类型错误
name="张",
email="invalid-email",
age=200
)
except Exception as e:
print(f"验证失败: {e}")13. 小结
| 概念 | 语法 | 说明 |
|---|---|---|
| 基本类型 | str, int, float, bool |
内置类型直接使用 |
| Optional | X | None |
可空类型 |
| 容器 | list[T], dict[K, V] |
泛型容器 |
| Callable | Callable[[A, B], R] |
函数类型 |
| TypeVar | TypeVar('T') |
泛型类型变量 |
| Protocol | class P(Protocol) |
结构化接口 |
| Literal | Literal["a", "b"] |
字面量类型 |
| Final | Final[int] |
不可变常量 |
| mypy | mypy src/ |
静态类型检查器 |
14. 练习题
- 为之前写的所有函数添加类型提示,并用 mypy 检查。
- 实现一个泛型缓存类
Cache[K, V],支持 get/set/delete。 - 使用 Protocol 定义一个
Iterable接口,并实现它。 - 使用 Pydantic 创建一个 API 请求模型,包含数据验证。
下节预告:我们将学习代码风格规范和 Linting 工具,让代码更加整洁专业。