(十)给函数写类型注解
1. 参数、返回值与容器元素
函数多起来后,仅看 def summarize(values),还不容易知道它接收什么。类型注解把预期的数据形状写在接口旁边:
# : 后标出参数类型,-> 后标出返回值类型;函数体仍按普通语句执行。
def summarize(values: list[int]) -> tuple[int, float]:
total = sum(values)
average = total / len(values) if values else 0.0
return total, average
result: tuple[int, float] = summarize([20, 35])
print(result)输出 (55, 27.5)。list[int] 表示整数列表;tuple[int, float] 表示固定两个位置的元组;dict[str, int] 表示字符串键与整数值;tuple[int, ...] 则表示各项为整数、项数可变的元组。箭头的 -> 写在定义中,不是把某个值赋给类型。
注解会帮助读者、编辑器与类型检查工具理解接口,普通 Python 调用不会自动逐项检查它。 下面的函数只把对象原样交回,因此传入字符串也照常返回字符串:
# 观察注解与执行的区别:本函数没有编写运行时数据检查。
def keep(value: int) -> int:
return value
print(keep("35"))
print(type(keep("35")))输出 35 和 <class 'str'>。希望把字符串转成整数,要实际执行 int(...);需要验证数据,则要写判断或使用后面介绍的校验工具。Web 框架读取注解并进行处理,是框架额外完成的工作。类型注解说明
2. 联合类型、类型别名与泛型函数
查找记录时,可能得到一项,也可能没有找到,可以用 | 表示两种结果:
# int | None 表示结果是整数或 None;找到 0 时也能与未找到区分。
def find_minutes(records: dict[str, int], title: str) -> int | None:
return records.get(title)
print(find_minutes({"Python": 0}, "Python"))
print(find_minutes({"Python": 0}, "NumPy"))结果是 0 与 None。X | None 说明值的类型,不表示调用时可以省略这个参数;是否能省略仍由默认值决定。变量中真实保存了什么,也仍由执行过程决定。
Python 3.12 可以用 type 语句给较长的类型表达式起名字,也可以在函数名后的方括号中声明类型参数:
# 3.12 的 type 语句建立类型别名,便于在多个注解中复用同一种形状。
type Reading = tuple[str, int]
def get_minutes(record: Reading) -> int:
return record[1]
# T 表示一次使用中相互对应的元素类型,不固定成整数或字符串。
def first[T](values: list[T]) -> T | None:
if not values:
return None
return values[0]
print(get_minutes(("Python", 35)))
print(first([20, 35]), first(["Python", "NumPy"]), first([]))输出 35,以及 20 Python None。Reading 是供注解复用的类型别名,并没有建立一个用于存储记录的新类。T 把“传入列表的元素类型”和“返回元素的类型”联系起来;first([20, 35]) 的元素是整数,first(["Python"]) 的元素是字符串。调用时正常写函数名和圆括号,不需要把类型写进实参列表。Python 3.12 类型参数语法
3. 标注传入的函数与可迭代数据
从 collections.abc 导入 Callable,可以说明回调接收什么、返回什么。from 模块 import 名称 正是上一节建立当前文件绑定的写法。
# Callable[[int], bool] 表示接收一个 int、返回 bool 的可调用对象。
from collections.abc import Callable
def count_matching(values: list[int], predicate: Callable[[int], bool]) -> int:
return sum(predicate(value) for value in values)
def is_met(minutes: int) -> bool:
return minutes >= 30
print(count_matching([20, 35, 45], is_met))得到 2。可调用对象是能放在圆括号前接受调用的对象,本节用函数作为例子。后面定义类时还会见到其他可调用对象。这里只需要遍历数据时,也可以用 Iterable[int] 表达“可以逐项取得整数”;下一节再把可迭代对象与迭代器的区别展开。collections.abc 接口