Python 现代开发学习笔记

(十)给函数写类型注解

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 接口

笔记正文与全部题解 · Ctrl / ⌘ K