类型标注
类型标注 = 给变量、参数、返回值写上类型说明 。
它不会在运行时强制检查,主要给 IDE / 类型检查工具用,用来:
- 写代码时自动补全、提示参数
- 提前发现「传错类型」这类问题
为什么需要
没有标注时,调用方不知道该传什么,也容易写出「能跑但不符合预期」的代码:
python
# 问题:参数类型不明确
def add(a, b):
return a + b
# 调用者不知道应该传什么类型
add(1, 2) # 3
add("1", "2") # "12" ------ 这也是合法的,但可能不是预期行为
add([1], [2]) # [1, 2] ------ 同样合法
# 没有类型提示,难以在编码时发现错误
基础写法
变量
python
# 声明变量的类型
name: str = "Alice"
age: int = 25
pi: float = 3.14
is_active: bool = True
# 没有初始值
value: int
value = 10
# Python 是动态语言,类型标注不会强制约束
x: int = "hello" # 不会报错,但类型检查工具会提示
要点:写了 x: int,运行时照样可以塞字符串;要靠类型检查工具(或 IDE)才会报警。
函数
格式:参数: 类型,返回值写在 -> 后面。
python
def greet(name: str, age: int) -> str:
"""函数参数和返回值的类型标注"""
return f"{name} 今年 {age} 岁
# 调用
greet("Alice", 25) # 正确
greet("Alice", "25") # 运行不会报错,但类型检查会警告
如果函数永远不会正常返回 (比如直接退出程序),用 NoReturn:
python
from typing import NoReturn
def exit_program() -> NoReturn:
"""表示函数永远不会正常返回"""
import sys
sys.exit(1)
常用复合类型
Optional 和 Union
Optional:可能有值,也可能没有
Optional[X] = 要么是 X,要么是 None。
下面三种写法意思完全一样:
python
Optional[str]
Union[str, None]
str | None # Python 3.10+
典型场景:查找用户 ------ 找到返回名字,找不到返回 None:
python
from typing import Optional, Union
# Optional:值可以是某个类型,也可以是 None
def find_user(user_id: int) -> Optional[str]:
"""返回用户名,找不到时返回 None"""
if user_id <= 0:
return None
return f"User_{user_id}"
调用时先判断是不是 None,再当字符串用:
python
name = find_user(1)
if name is None:
print("没找到")
else:
print(name) # 这里才是确定的 str
Union:多种类型之一
Union[A, B]:可以是 A,也可以是 B。
python
# Union:值可以是多种类型之一
def parse_value(value: str) -> Union[int, float, str]:
"""尝试将字符串转换为数字,失败则返回原字符串"""
try:
if "." in value:
return float(value)
return int(value)
except ValueError:
return value
新写法也可写成:
int | float | str(Python 3.10+)
容器类型
在 List、Dict 等后面用 [] 写明里面装什么:
python
from typing import List, Dict, Tuple, Set
# 列表:元素类型
scores: List[int] = [85, 90, 78]
names: List[str] = ["Alice", "Bob", "Charlie"]
# 字典:键类型, 值类型
student_scores: Dict[str, int] = {
"Alice": 85,
"Bob": 90,
}
# 元组:固定长度,每个位置类型可不同
point: Tuple[int, int] = (10, 20)
person: Tuple[str, int, bool] = ("Alice", 25, True)
# 集合:元素类型
tags: Set[str] = {"python", "typing", "type-hints"}
Any 和类型别名
Any:随便什么类型都行(等于几乎不检查,少用)- 类型别名:给复杂类型起个好记的名字
python
from typing import Any, TypeAlias
# Any:任意类型,相当于没有类型约束
def log_data(data: Any) -> None:
print(f"数据: {data}")
# 类型别名,让复杂类型更易读
Vector: TypeAlias = List[float]
Matrix: TypeAlias = List[List[float]]
def dot_product(v1: Vector, v2: Vector) -> float:
"""计算两个向量的点积"""
return sum(a * b for a, b in zip(v1, v2))
类里怎么标
- 方法参数、返回值照常写
Self:返回「自己这个类型的实例」- 类还没定义完就要引用自己时,可写成字符串
"Point"
python
from typing import Self
class Point:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def move(self, dx: float, dy: float) -> Self:
"""返回移动后的新点"""
return Point(self.x + dx, self.y + dy)
def distance_to(self, other: "Point") -> float:
"""计算到另一个点的距离"""
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
# 使用
p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance_to(p2)) # 5.0
泛型
为什么需要泛型?
假设有一个函数,取出列表的第一个元素:
python
# 不用泛型:返回类型丢失了
def get_first(items):
return items[0]
# 你无法知道返回的到底是什么类型
有了泛型:输入什么类型,输出就是什么类型,类型信息不会丢。
传统写法(Python 3.12 之前)
需要两个核心组件:TypeVar 和 Generic。
TypeVar --- 类型变量
T 代表「未来某个具体类型」,实际使用时才会被确定(类似数学里的未知数 x):
python
from typing import TypeVar
T = TypeVar('T') # 定义一个类型变量
泛型函数
python
from typing import TypeVar, List
T = TypeVar('T')
def get_first_item(lst: List[T]) -> T:
return lst[0]
# 调用时,类型自动推断
num = get_first_item([1, 2, 3]) # num 的类型是 int
text = get_first_item(['a', 'b', 'c']) # text 的类型是 str
泛型类
python
from typing import TypeVar, Generic
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self, content: T):
self.content = content
def get(self) -> T:
return self.content
# 使用
int_box = Box(42) # Box[int]
str_box = Box("hello") # Box[str]
现代写法(Python 3.12+)
可以直接在类 / 函数名后面声明类型参数,不必再单独写 TypeVar + Generic:
python
# 以前
from typing import TypeVar, Generic
T = TypeVar('T')
class Box(Generic[T]):
def get(self) -> T: ...
# 现在(Python 3.12+)
class Box[T]:
def get(self) -> T: ...
# 函数也可以这样写
def get_first[T](lst: list[T]) -> T:
return lst[0]
更接近 Java / C# 的写法,类型参数不再游离在类 / 函数外面。
TypeVar 的三种约束方式
1. 无约束(任意类型)
python
T = TypeVar('T') # 可以是任何类型
2. bound 约束(必须是某类型的子类)
python
S = TypeVar('S', bound=str) # 必须是 str 或其子类
def print_capitalized(x: S) -> S:
print(x.capitalize())
return x
3. 值约束(只能是指定的几种类型之一)
python
A = TypeVar('A', str, bytes) # 只能是 str 或 bytes
def concatenate(x: A, y: A) -> A:
return x + y
concatenate("hello", "world") # OK,返回 str
concatenate(b"foo", b"bar") # OK,返回 bytes
# concatenate("foo", b"bar") # 类型检查报错,不能混用
日常写业务代码,多数时候用
list[int]、str | None就够了。自己写「输入什么类型、输出就保持什么类型」的函数 / 类时,才需要泛型。
Callable:参数本身是函数
Callable[[参数类型...], 返回类型] 用来标注「回调函数长什么样」。
python
from typing import Callable
def execute_callback(
callback: Callable[[int, int], int],
a: int,
b: int
) -> int:
"""执行回调函数"""
return callback(a, b)
# 使用
result = execute_callback(lambda x, y: x + y, 3, 5)
print(result) # 8
读法:Callable[[int, int], int] = 接收两个 int,返回一个 int 的函数。
实际能干什么
1. 用 TypedDict 描述「字典长什么样」
普通 dict 太松;TypedDict 可以规定每个 key 的类型:
python
from typing import TypedDict
class UserResponse(TypedDict):
"""API 返回的用户数据结构"""
id: int
name: str
email: str
is_active: bool
def get_user(user_id: int) -> UserResponse:
return {
"id": user_id,
"name": "Alice",
"email": "alice@example.com",
"is_active": True,
}
某个字段可以不提供时,用 NotRequired:
python
from typing import TypedDict, NotRequired
class Product(TypedDict):
id: int
name: str
description: NotRequired[str] # 可选字段,可以不提供
# 两种写法都合法
p1: Product = {"id": 1, "name": "手机"}
p2: Product = {"id": 2, "name": "电脑", "description": "高性能笔记本"}
反过来,希望默认所有字段都可选 时,用 total=False:
python
class Config(TypedDict, total=False):
debug: bool
retry_times: int
timeout: float
# 所有字段都可以不提供
c: Config = {}