跳到主要内容

Python 类型注解

类型注解(Type Hints)是 Python 3.5 引入的功能,可以为变量、函数参数和返回值添加类型信息。这是一种可选的静态类型系统,运行时不强制执行类型检查,但可以被类型检查器(如 mypy)、IDE 和 linter 使用,帮助开发者在编码阶段发现潜在的类型错误。

为什么使用类型注解?

提高代码可读性

类型注解明确表达了函数的输入输出类型,使代码意图更加清晰:

# 没有类型注解 - 参数和返回值类型不明确
def calculate_discount(price, rate):
return price * rate

# 有类型注解 - 一目了然
def calculate_discount(price: float, rate: float) -> float:
return price * rate

静态类型检查

配合 mypy、pyright 等工具,可以在编码阶段发现类型错误,而不是等到运行时才暴露问题:

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

# 类型检查器会报错
result: int = greet("Alice") # Error: 返回值是 str,不是 int
greet(123) # Error: 参数应该是 str,不是 int

IDE 支持

类型注解让 IDE 能够提供更准确的代码补全、参数提示和错误标记:

def process_data(data: list[str]) -> dict[str, int]:
result = {}
for item in data:
# IDE 知道 item 是 str,提供字符串方法补全
result[item.lower()] = len(item)
return result

文档作用

类型注解本身就是很好的文档,减少了写额外注释的需要:

def fetch_user(
user_id: int,
include_deleted: bool = False
) -> dict[str, str | int] | None:
"""获取用户信息。"""
...

基本类型注解

变量注解

# 基本类型
name: str = "张三"
age: int = 25
price: float = 19.99
is_active: bool = True

# 容器类型(Python 3.9+)
numbers: list[int] = [1, 2, 3]
scores: dict[str, int] = {"Alice": 90, "Bob": 85}
unique_ids: set[int] = {1, 2, 3}

# 兼容旧版本的写法(Python 3.5-3.8)
from typing import List, Dict, Set
numbers: List[int] = [1, 2, 3]
scores: Dict[str, int] = {"Alice": 90}
unique_ids: Set[int] = {1, 2, 3}

函数注解

# 基本函数注解
def greet(name: str) -> str:
return f"Hello, {name}!"

# 多个参数
def add(a: int, b: int) -> int:
return a + b

# 无返回值
def log_message(message: str) -> None:
print(message)

# 默认参数
def create_user(name: str, age: int = 0) -> dict[str, str | int]:
return {"name": name, "age": age}

类属性注解

class Person:
# 类属性
species: str = "人类"

# 实例属性(在 __init__ 中赋值)
name: str
age: int

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

def introduce(self) -> str:
return f"我是{self.name},今年{self.age}岁"

# 使用 dataclass 简化
from dataclasses import dataclass

@dataclass
class Student:
name: str
age: int
grade: str = "大一"

类型别名

类型别名用于简化复杂的类型声明,让代码更易读。

type 语句(Python 3.12+)

Python 3.12 引入了 type 语句来声明类型别名:

# 简单类型别名
type Point = tuple[float, float]
type UserId = int
type JsonValue = str | int | float | bool | None | list['JsonValue'] | dict[str, 'JsonValue']

# 使用类型别名
def distance(p1: Point, p2: Point) -> float:
return ((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5

# 泛型类型别名
type Vector[T] = list[tuple[T, T]]
type Result[T, E] = tuple[T, None] | tuple[None, E]

coordinates: Vector[float] = [(1.0, 2.0), (3.0, 4.0)]

传统类型别名(Python 3.5+)

from typing import TypeAlias, Union

# 简单赋值
Point = tuple[float, float]

# 显式标注(Python 3.10+)
UserId: TypeAlias = int
JsonValue: TypeAlias = Union[
str, int, float, bool, None,
list['JsonValue'], dict[str, 'JsonValue']
]

NewType:创建区分类型

NewType 用于创建在类型层面区分、但运行时实际上是原类型的"新类型"。这在需要避免混淆相似类型时非常有用。

from typing import NewType

# 创建区分类型
UserId = NewType('UserId', int)
ProductId = NewType('ProductId', int)

def get_user(user_id: UserId) -> str:
return f"User {user_id}"

def get_product(product_id: ProductId) -> str:
return f"Product {product_id}"

# 正确使用
user_id = UserId(12345)
product_id = ProductId(67890)

get_user(user_id) # OK
get_product(product_id) # OK

# 类型错误 - 不能混淆
get_user(product_id) # Error: 期望 UserId,实际是 ProductId
get_product(user_id) # Error: 期望 ProductId,实际是 UserId

# 运行时仍然是 int
print(type(user_id)) # <class 'int'>
print(user_id + 1) # 12346 - 可以进行 int 运算

应用场景

  • 区分不同含义的 ID(用户 ID、订单 ID、产品 ID)
  • 区分不同单位的数值(米、秒、像素)
  • 防止参数传递错误
from typing import NewType

Seconds = NewType('Seconds', float)
Milliseconds = NewType('Milliseconds', float)

def wait(duration: Seconds) -> None:
print(f"等待 {duration} 秒")

# 类型检查器会警告
wait(Milliseconds(500)) # Error: 类型不匹配

联合类型

Union 类型

Union 表示值可能是多种类型之一:

from typing import Union

def process(value: Union[int, str, float]) -> str:
return str(value)

# Python 3.10+ 可以使用 | 语法(推荐)
def process(value: int | str | float) -> str:
return str(value)

Optional 类型

Optional[T] 等价于 T | None,表示值可能为 None

from typing import Optional

def find_user(user_id: int) -> Optional[str]:
if user_id > 0:
return "张三"
return None

# Python 3.10+ 推荐写法
def find_user(user_id: int) -> str | None:
if user_id > 0:
return "张三"
return None

# 使用时需要检查 None
user = find_user(1)
if user is not None:
print(user.upper()) # 安全:user 确定不是 None

None 如何影响类型推断

def get_length(items: list[str] | None) -> int:
# 类型检查器知道这里 items 可能是 None
if items is None:
return 0
# 类型收窄:这里 items 确定是 list[str]
return len(items)

特殊类型

Any 类型

Any 表示任意类型,关闭类型检查。它是类型的"逃生舱",但应谨慎使用:

from typing import Any

def log_value(value: Any) -> None:
# 可以对 Any 类型进行任何操作
print(value)
print(value.any_method()) # 类型检查器不会报错

# 对比 object
def safe_log(value: object) -> None:
print(value)
# print(value.upper()) # Error: object 没有 upper 方法

Any vs object

类型特点使用场景
Any可以进行任何操作动态类型代码、第三方库接口
object只能使用 object 方法需要类型安全地接受任意值

Never 和 NoReturn

NeverNoReturn 表示"永远不会返回"的函数:

from typing import Never, NoReturn

def error_exit(message: str) -> Never:
"""永远不会返回,总是抛出异常"""
raise SystemExit(message)

def infinite_loop() -> Never:
"""永远不会返回,无限循环"""
while True:
pass

def assert_never(value: Never) -> Never:
"""用于穷尽性检查"""
raise AssertionError(f"Unexpected value: {value}")

# 用于 match 语句的穷尽性检查
from typing import Literal

Color = Literal["red", "green", "blue"]

def handle_color(color: Color) -> str:
match color:
case "red":
return "红色"
case "green":
return "绿色"
case "blue":
return "蓝色"
case _:
# 如果添加了新颜色但忘记处理,这里会报错
assert_never(color)

Self 类型

Self 表示当前类的实例类型,特别适用于返回 self 的方法或工厂方法:

from typing import Self

class Shape:
def __init__(self, width: int, height: int):
self.width = width
self.height = height

def scale(self, factor: float) -> Self:
"""返回自身的缩放版本"""
return self.__class__(int(self.width * factor), int(self.height * factor))

@classmethod
def square(cls, size: int) -> Self:
"""工厂方法:创建正方形"""
return cls(size, size)

def __enter__(self) -> Self:
return self

class Rectangle(Shape):
pass

# 正确的类型推断
rect: Rectangle = Rectangle(10, 20)
scaled: Rectangle = rect.scale(2) # 类型检查器知道返回 Rectangle
square: Rectangle = Rectangle.square(5) # 也正确

LiteralString

LiteralString 表示只能是字面字符串,用于安全敏感的 API:

from typing import LiteralString

def execute_sql(sql: LiteralString) -> None:
"""执行 SQL 查询,只接受字面字符串防止注入"""
print(f"执行: {sql}")

def dangerous_query(user_input: str) -> None:
# 安全:字面字符串
execute_sql("SELECT * FROM users")

# 安全:字面字符串拼接
table = "users"
execute_sql(f"SELECT * FROM {table}")

# 错误:用户输入不是字面字符串
# execute_sql(f"SELECT * FROM users WHERE name = '{user_input}'")

# 错误:普通字符串变量
# execute_sql(user_input)

Literal 类型

Literal 限制值只能是特定的字面值:

from typing import Literal

Direction = Literal["up", "down", "left", "right"]
Status = Literal["pending", "active", "inactive"]

def move(direction: Direction) -> None:
print(f"移动方向: {direction}")

def set_status(status: Status) -> None:
print(f"状态: {status}")

move("up") # OK
move("down") # OK
# move("forward") # Error: 不是有效的方向

# 数字字面量
Size = Literal[1, 2, 3, 4, 5]

def set_font_size(size: Size) -> None:
print(f"字体大小: {size}")

# 混合类型
Result = Literal["success", "error", 0, 1]

容器类型

列表、集合、字典

# Python 3.9+ 内置泛型(推荐)
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 90, "Bob": 85}
unique_ids: set[int] = {1, 2, 3}

# Python 3.5-3.8 使用 typing 模块
from typing import List, Dict, Set
names: List[str] = ["Alice", "Bob"]
scores: Dict[str, int] = {"Alice": 90}
unique_ids: Set[int] = {1, 2, 3}

元组

元组有特殊的类型注解方式,支持固定长度和可变长度:

# 固定长度元组 - 每个位置的类型都明确
point: tuple[float, float] = (1.0, 2.0)
person: tuple[str, int, str] = ("张三", 25, "北京")

# 可变长度元组 - 所有元素同一类型
numbers: tuple[int, ...] = (1, 2, 3, 4, 5)

# 空元组
empty: tuple[()] = ()

# 解构
def get_point() -> tuple[float, float]:
return (1.0, 2.0)

x, y = get_point() # x: float, y: float

Callable 可调用对象

from collections.abc import Callable

# Callable[[参数类型], 返回类型]
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)

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

result = apply(add, 3, 5) # 8

# 无参数的回调
def on_click(callback: Callable[[], None]) -> None:
callback()

# 任意参数列表
handler: Callable[..., str] = lambda *args: str(args)

# 带默认参数
def process(
data: list[int],
transform: Callable[[int], int] = lambda x: x * 2
) -> list[int]:
return [transform(x) for x in data]

生成器和协程

from collections.abc import Generator, Iterator, AsyncGenerator, AsyncIterator

# 简单生成器
def count_up(n: int) -> Iterator[int]:
for i in range(n):
yield i

# 完整生成器类型 Generator[YieldType, SendType, ReturnType]
def echo_round() -> Generator[int, float, str]:
sent = yield 0
while sent >= 0:
sent = yield round(sent)
return "Done"

# 异步生成器
async def async_count(n: int) -> AsyncIterator[int]:
for i in range(n):
yield i

# 带发送类型的异步生成器
async def async_echo() -> AsyncGenerator[int, str]:
received = yield 0
while True:
received = yield int(received)

泛型(Generics)

泛型允许编写可以处理多种类型的代码,同时保持类型安全。

TypeVar 类型变量

from typing import TypeVar

# 基本类型变量
T = TypeVar('T')

def first_element(lst: list[T]) -> T | None:
"""返回列表的第一个元素"""
return lst[0] if lst else None

# 使用
numbers = [1, 2, 3]
first: int = first_element(numbers) # 推断为 int

strings = ["a", "b", "c"]
first_str: str = first_element(strings) # 推断为 str

# 有界类型变量
Number = TypeVar('Number', int, float)

def add_numbers(a: Number, b: Number) -> Number:
return a + b

add_numbers(1, 2) # OK
add_numbers(1.5, 2.5) # OK
# add_numbers("a", "b") # Error: 不是 int 或 float

# 绑定到基类
class Animal:
def speak(self) -> str:
return "..."

class Dog(Animal):
def speak(self) -> str:
return "Woof!"

A = TypeVar('A', bound=Animal)

def make_sound(animal: A) -> str:
return animal.speak()

泛型类

from typing import TypeVar, Generic

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 is_empty(self) -> bool:
return len(self._items) == 0

# 使用
int_stack: Stack[int] = Stack()
int_stack.push(1)
int_stack.push(2)
print(int_stack.pop()) # 2

str_stack: Stack[str] = Stack()
str_stack.push("hello")
print(str_stack.pop()) # hello

# 多类型参数
K = TypeVar('K')
V = TypeVar('V')

class Pair(Generic[K, V]):
def __init__(self, key: K, value: V):
self.key = key
self.value = value

pair: Pair[str, int] = Pair("age", 25)

Python 3.12+ 新语法(PEP 695)

Python 3.12 引入了更简洁的泛型语法,无需导入 TypeVarGeneric

# 泛型函数
def first[T](elements: list[T]) -> T | None:
return elements[0] if elements else None

# 泛型类
class Stack[T]:
def __init__(self):
self._items: list[T] = []

def push(self, item: T) -> None:
self._items.append(item)

def pop(self) -> T:
return self._items.pop()

# 类型约束
def process[T: (int, float)](value: T) -> T:
return value

# 边界限制
class Container[T: str]:
def __init__(self, value: T):
self.value = value

# 多类型参数
class Map[K, V]:
def __init__(self):
self._data: dict[K, V] = {}

def put(self, key: K, value: V) -> None:
self._data[key] = value

def get(self, key: K) -> V | None:
return self._data.get(key)

# 泛型类型别名
type Vector[T] = list[tuple[T, T]]
type Result[T, E] = tuple[T, None] | tuple[None, E]

Protocol:结构化子类型

Protocol 允许基于结构(方法/属性)而非继承来判断类型兼容性,即"鸭子类型"的静态版本。

基本用法

from typing import Protocol

class Drawable(Protocol):
def draw(self) -> None:
...

class Circle:
def draw(self) -> None:
print("绘制圆形")

class Square:
def draw(self) -> None:
print("绘制正方形")

def render(shape: Drawable) -> None:
shape.draw()

# Circle 和 Square 没有继承 Drawable,但都有 draw 方法
render(Circle()) # OK
render(Square()) # OK

class NotDrawable:
pass

# render(NotDrawable()) # Error: 没有 draw 方法

带属性的 Protocol

from typing import Protocol

class Named(Protocol):
name: str
age: int

class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age

class Animal:
def __init__(self, name: str, age: int):
self.name = name
self.age = age

def greet(entity: Named) -> str:
return f"Hello, {entity.name} ({entity.age})"

greet(Person("Alice", 30)) # OK
greet(Animal("Buddy", 5)) # OK

runtime_checkable

使用 @runtime_checkable 装饰器后,可以使用 isinstance() 检查:

from typing import Protocol, runtime_checkable

@runtime_checkable
class Sized(Protocol):
def __len__(self) -> int:
...

class MyList:
def __len__(self) -> int:
return 42

print(isinstance(MyList(), Sized)) # True
print(isinstance([1, 2, 3], Sized)) # True
print(isinstance(42, Sized)) # False

TypedDict

TypedDict 用于精确标注字典的结构,特别是 JSON 数据:

from typing import TypedDict, NotRequired, Required

class Person(TypedDict):
name: str
age: int
email: NotRequired[str] # 可选字段

# 使用
person: Person = {"name": "张三", "age": 25}
person_with_email: Person = {"name": "李四", "age": 30, "email": "[email protected]"}

# 读取
print(person["name"]) # str 类型

# total=False:所有字段都是可选的
class Config(TypedDict, total=False):
host: str
port: int
debug: bool

config: Config = {"host": "localhost"} # OK

# Required 和 NotRequired 混合使用
class PartialConfig(TypedDict, total=False):
host: Required[str] # 必需
port: int # 可选(继承 total=False)
debug: Required[bool] # 必需

ReadOnly 标记(Python 3.13+)

Python 3.13 引入了 typing.ReadOnly(PEP 705),用于标记 TypedDict 中只读的字段:

from typing import TypedDict, ReadOnly

class User(TypedDict):
id: ReadOnly[int] # 只读,创建后不可修改
name: str # 可读可写
email: str # 可读可写

# 创建时可以设置所有字段
user: User = {"id": 1, "name": "张三", "email": "[email protected]"}

# 可以修改可写字段
user["name"] = "李四" # OK
user["email"] = "[email protected]" # OK

# 类型检查器会警告修改只读字段
# user["id"] = 2 # Error: 无法修改只读字段

ReadOnly 与继承

ReadOnly 在继承中的行为:

from typing import TypedDict, ReadOnly, NotRequired

class BaseUser(TypedDict):
id: ReadOnly[int]
name: str

class ExtendedUser(BaseUser):
email: str
role: ReadOnly[str] # 新增只读字段

# ExtendedUser 相当于:
# class ExtendedUser(TypedDict):
# id: ReadOnly[int] # 从父类继承,保持只读
# name: str # 从父类继承,可写
# email: str # 新增,可写
# role: ReadOnly[str] # 新增,只读

ReadOnly 与 NotRequired 组合

from typing import TypedDict, ReadOnly, NotRequired

class APIResponse(TypedDict):
status: ReadOnly[str] # 只读,必需
code: ReadOnly[int] # 只读,必需
message: ReadOnly[NotRequired[str]] # 只读,可选
data: dict[str, object] # 可写,必需
metadata: NotRequired[dict[str, str]] # 可写,可选

# 正确使用
response: APIResponse = {
"status": "success",
"code": 200,
"data": {"result": "ok"}
}

# 可以修改可写字段
response["data"] = {"result": "updated"} # OK

# 不能修改只读字段(类型检查器会警告)
# response["status"] = "error" # Error

实际应用:API 响应

from typing import TypedDict, ReadOnly

class UserResponse(TypedDict):
id: int
username: str
email: str
is_active: bool

class PaginatedResponse(TypedDict):
data: list[UserResponse]
total: ReadOnly[int] # 只读,不应被客户端修改
page: int
per_page: int

def get_users(page: int = 1) -> PaginatedResponse:
# 模拟 API 响应
return {
"data": [
{"id": 1, "username": "alice", "email": "[email protected]", "is_active": True},
{"id": 2, "username": "bob", "email": "[email protected]", "is_active": False}
],
"total": 100,
"page": page,
"per_page": 10
}

response = get_users()
for user in response["data"]:
print(f"{user['username']}: {user['email']}")

不可变数据模型

使用 ReadOnly 创建不可变的配置或数据模型:

from typing import TypedDict, ReadOnly

class ServerConfig(TypedDict):
"""服务器配置,所有字段只读"""
host: ReadOnly[str]
port: ReadOnly[int]
max_connections: ReadOnly[int]
timeout: ReadOnly[float]

def create_server(config: ServerConfig) -> None:
# 配置应该是不可变的,防止意外修改
print(f"启动服务器: {config['host']}:{config['port']}")

# 配置一旦创建就不应修改
config: ServerConfig = {
"host": "localhost",
"port": 8080,
"max_connections": 100,
"timeout": 30.0
}

create_server(config)
# config["port"] = 9090 # 类型检查器会警告

ParamSpec 和 Concatenate

ParamSpec 用于捕获函数的参数签名,Concatenate 用于添加参数:

from typing import ParamSpec, Concatenate, TypeVar, Callable

P = ParamSpec('P')
R = TypeVar('R')

def with_logging(
func: Callable[P, R]
) -> Callable[P, R]:
"""装饰器:添加日志"""
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"调用 {func.__name__}")
result = func(*args, **kwargs)
print(f"返回 {result}")
return result
return wrapper

@with_logging
def add(a: int, b: int) -> int:
return a + b

# Concatenate:添加参数
from collections.abc import Callable

def with_context(
func: Callable[Concatenate[str, P], R]
) -> Callable[P, R]:
"""装饰器:注入上下文参数"""
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
context = "default_context"
return func(context, *args, **kwargs)
return wrapper

@with_context
def process(context: str, data: str) -> str:
return f"[{context}] {data}"

@override 装饰器(Python 3.12+)

@override 明确标记方法是覆盖父类的方法,类型检查器会验证:

from typing import override

class Animal:
def speak(self) -> str:
return "..."

def move(self) -> None:
pass

class Dog(Animal):
@override
def speak(self) -> str:
return "Woof!"

# 错误:父类没有 run 方法
# @override
# def run(self) -> None:
# pass

class Cat(Animal):
@override
def speak(self) -> str:
return "Meow!"

@deprecated 装饰器(Python 3.13+)

Python 3.13 在 warnings 模块中引入了 @deprecated 装饰器(PEP 702),用于在类型系统和运行时标记废弃的函数和类:

基本用法

from warnings import deprecated
from typing import TypeAlias

@deprecated("使用 new_function() 替代")
def old_function(x: int) -> int:
"""这个函数已废弃"""
return x * 2

def new_function(x: int) -> int:
return x * 3

# 调用时会触发 DeprecationWarning
result = old_function(5) # 发出警告

废弃类

@deprecated("使用 NewClass 替代")
class OldClass:
def __init__(self, value: int):
self.value = value

class NewClass:
def __init__(self, value: int):
self.value = value * 2

# 实例化时发出警告
obj = OldClass(10) # DeprecationWarning

废弃方法

class Calculator:
def add(self, a: int, b: int) -> int:
return a + b

@deprecated("使用 add() 方法替代")
def sum(self, a: int, b: int) -> int:
"""已废弃,请使用 add() 方法"""
return self.add(a, b)

calc = Calculator()
calc.sum(1, 2) # DeprecationWarning

类型检查器支持

@deprecated 装饰器与类型检查器集成,在静态分析时就会报告废弃警告:

from warnings import deprecated

@deprecated("将在 2.0 版本中移除")
def legacy_api() -> str:
return "legacy"

# 类型检查器(如 mypy、pyright)会在编辑时显示警告
# 使用废弃函数会在 IDE 中显示删除线
result = legacy_api()

迁移示例

from warnings import deprecated
from typing import overload

# 旧 API 废弃,引导用户迁移到新 API
@deprecated("""
此函数已废弃,请使用 process_data() 替代。
迁移指南:
- old_process(data, True) → process_data(data, mode="strict")
- old_process(data, False) → process_data(data, mode="lenient")
""")
def old_process(data: list[str], strict: bool = True) -> dict[str, int]:
mode = "strict" if strict else "lenient"
return process_data(data, mode=mode)

def process_data(data: list[str], mode: str = "strict") -> dict[str, int]:
"""新的数据处理函数"""
if mode == "strict":
return {s: len(s) for s in data if s}
return {s: len(s) for s in data}

与 typing 组合使用

from warnings import deprecated
from typing import TypeAlias, Callable

# 废弃类型别名
@deprecated("使用 UserID 替代")
type OldUserID = str

type UserID = int

# 废弃回调类型
@deprecated("使用 modern_callback 类型替代")
OldCallback: TypeAlias = Callable[[str], None]

type ModernCallback = Callable[[str, dict[str, object]], None]

最佳实践

from warnings import deprecated
from typing import overload

class APIClient:
@overload
@deprecated("使用 get(url, params=None) 替代")
def fetch(self, url: str) -> dict: ...

@overload
def get(self, url: str, params: dict[str, str] | None = None) -> dict: ...

def fetch(self, url: str) -> dict:
"""废弃的方法,重定向到新方法"""
return self.get(url)

def get(self, url: str, params: dict[str, str] | None = None) -> dict:
"""推荐使用的新方法"""
# 实际实现
return {"url": url, "params": params}

注意事项

  • @deprecated 装饰器应放在最外层(在 @overload 之后)
  • 废弃消息应清晰说明替代方案和迁移路径
  • 可以在消息中包含版本信息和截止日期

类型收窄(Type Narrowing)

类型检查器可以通过条件判断自动收窄类型:

from typing import Union

def process(value: Union[int, str]) -> str:
# 类型检查器根据 isinstance 自动收窄
if isinstance(value, int):
# 这里 value 是 int 类型
return str(value * 2)
else:
# 这里 value 是 str 类型
return value.upper()

# None 检查
def handle_optional(value: str | None) -> str:
if value is None:
return "empty"
# 这里 value 确定是 str
return value.upper()

TypeGuard

TypeGuard 用于自定义类型守卫函数:

from typing import TypeGuard, Any

def is_string_list(value: list[Any]) -> TypeGuard[list[str]]:
"""检查列表是否全是字符串"""
return all(isinstance(x, str) for x in value)

def process(items: list[Any]) -> None:
if is_string_list(items):
# 这里 items 被收窄为 list[str]
print(items[0].upper()) # OK

TypeIs(Python 3.13+)

TypeIs 是 Python 3.13 引入的新特性,比 TypeGuard 提供更直观的类型收窄行为。

TypeIs vs TypeGuard

TypeGuardTypeIs 的关键区别在于类型收窄的方向:

  • TypeGuard:当守卫函数返回 True 时,类型被收窄为指定的类型
  • TypeIs:更符合直觉,返回 True 时类型被收窄,返回 False 时类型被排除
from typing import TypeGuard, TypeIs

# TypeGuard 示例
def is_str_typeguard(value: str | int) -> TypeGuard[str]:
return isinstance(value, str)

def process_typeguard(value: str | int) -> None:
if is_str_typeguard(value):
# value 被收窄为 str
print(value.upper())
else:
# value 仍然是 str | int(TypeGuard 的限制)
# 类型检查器不知道这里 value 一定是 int
pass

# TypeIs 示例(Python 3.13+)
def is_str_typeis(value: str | int) -> TypeIs[str]:
return isinstance(value, str)

def process_typeis(value: str | int) -> None:
if is_str_typeis(value):
# value 被收窄为 str
print(value.upper())
else:
# value 被收窄为 int(TypeIs 的优势)
print(value * 2) # 安全:value 确定是 int

实际应用示例

from typing import TypeIs

def is_non_empty_string(value: str | None) -> TypeIs[str]:
"""检查值是否为非空字符串"""
return value is not None and len(value) > 0

def process_input(value: str | None) -> str:
if is_non_empty_string(value):
# value 确定是非空字符串
return value.upper()
else:
# value 是 None 或空字符串
return "默认值"

def is_positive(n: int) -> TypeIs[int]:
"""检查整数是否为正数"""
return n > 0

def process_number(n: int) -> str:
if is_positive(n):
# n 确定是正整数
return f"正数: {n}"
else:
# n 确定是非正整数(0 或负数)
return f"非正数: {n}"

# 复杂类型检查
def is_user_dict(value: dict[str, object]) -> TypeIs[dict[str, str]]:
"""检查字典的所有值是否都是字符串"""
return all(isinstance(v, str) for v in value.values())

def process_data(data: dict[str, object]) -> None:
if is_user_dict(data):
# data 确定是 dict[str, str]
for key, value in data.items():
print(f"{key}: {value.upper()}") # 安全调用字符串方法

TypeIs 的设计原理

TypeIs 的名称和语义来自类型理论中的"类型即命题"对应关系:

  • 类型 T 对应命题"值 x 的类型是 T"
  • TypeIs[T] 意味着"返回值断言参数的类型是否为 T"

这种设计让类型收窄更加符合直觉,特别是在 else 分支中。

类型注解最佳实践

1. 逐步添加类型

# 从无类型开始
def process(data):
return data.get("value", 0)

# 逐步添加参数类型
def process(data: dict) -> int:
return data.get("value", 0)

# 最终完善
def process(data: dict[str, int]) -> int:
return data.get("value", 0)

2. 使用类型别名简化复杂类型

# 不好:复杂的嵌套类型
def process(
data: dict[str, list[tuple[str, int, dict[str, float]]]]
) -> None:
pass

# 好的:使用类型别名
type Record = tuple[str, int, dict[str, float]]
type DataStore = dict[str, list[Record]]

def process(data: DataStore) -> None:
pass

3. 避免过度使用 Any

# 不好
def process(data: Any) -> Any:
return data

# 好的:明确类型
def process(data: dict[str, int]) -> dict[str, int]:
return data

# 如果确实需要任意类型,使用泛型
def process[T](data: T) -> T:
return data

4. 使用 mypy 进行类型检查

# 安装
pip install mypy

# 运行类型检查
mypy your_file.py

# 严格模式
mypy --strict your_file.py

5. 为第三方库添加类型存根

# third_party.pyi
def untyped_function(arg: int) -> str: ...

typing 模块常用类型速查表

类型说明示例
list[T]列表list[int]
dict[K, V]字典dict[str, int]
set[T]集合set[str]
tuple[T, ...]可变元组tuple[int, ...]
tuple[T1, T2]固定元组tuple[str, int]
Union[A, B] / A | B联合类型int | str
Optional[T] / T | None可选类型str | None
Any任意类型Any
NoReturn / Never永不返回def exit() -> Never
Literal[...]字面量Literal["a", "b"]
Callable[[Args], R]可调用对象Callable[[int], str]
Type[T] / type[T]类型对象type[int]
Protocol协议class Drawable(Protocol)
TypedDict类型字典class Person(TypedDict)
TypeVar类型变量T = TypeVar('T')
Generic[T]泛型基类class Stack(Generic[T])
NewType新类型UserId = NewType('UserId', int)
Self自身类型def method(self) -> Self
LiteralString字面字符串def sql(query: LiteralString)
ParamSpec参数规格P = ParamSpec('P')
Concatenate参数连接Callable[Concatenate[int, P], R]
TypeGuard类型守卫def is_str(x) -> TypeGuard[str]
TypeIs类型判断(3.13+)def is_str(x) -> TypeIs[str]
ReadOnly只读字段(3.13+)class D(TypedDict): x: ReadOnly[int]
Final最终值PI: Final = 3.14
@override覆盖标记@override def method(self)
@deprecated废弃标记(3.13+)@deprecated("使用 new_func 替代")

小结

本章系统学习了 Python 类型注解:

  1. 基本类型注解:变量、函数参数、返回值的类型标注
  2. 联合类型UnionOptional| 语法
  3. 特殊类型AnyNeverSelfLiteralStringLiteral
  4. 容器类型:列表、字典、集合、元组、Callable、生成器
  5. 泛型TypeVarGeneric 和 Python 3.12+ 新语法
  6. Protocol:结构化子类型,静态鸭子类型
  7. TypedDict:精确标注字典结构,Python 3.13+ 支持 ReadOnly
  8. 类型收窄TypeGuardTypeIs(Python 3.13+)
  9. 废弃标记@deprecated 装饰器(Python 3.13+)
  10. 最佳实践:逐步添加、使用别名、避免 Any

Python 版本特性对照

特性Python 版本
基本类型注解3.5+
UnionOptional3.5+
Literal3.8+
TypedDict3.8+
`` 联合类型语法
ParamSpecConcatenate3.10+
SelfLiteralString3.11+
@override3.12+
type 语句、类型参数语法3.12+
TypeIsReadOnly@deprecated3.13+

练习

  1. 为一个学生信息管理系统添加完整的类型注解
  2. 使用 Protocol 定义一个可序列化协议
  3. 使用 TypedDict 定义 REST API 响应类型,包含 ReadOnly 字段
  4. 创建一个泛型缓存类,支持类型安全的存取
  5. 使用 @override 实现一个继承层次结构
  6. 使用 @deprecated 标记一个废弃的函数,并提供迁移指南
  7. 使用 TypeIs 实现一个类型守卫函数,在 else 分支正确收窄类型

参考资料