码农高天
Python通过渐进式类型标注与mypy静态检查在编码阶段拦截缺陷,且完全不产生运行时性能损耗。
AI-assisted summary of 【python】Type Hint入门与初探,好好的python写什么类型标注? — original source. Check the original for full context.
Source published:
Key ideas
- Python 自 3.5 起引入 Type Hint,旨在解决大型项目与多人协作中因动态类型造成的代码可读性差及维护成本高问题。 (0:00)
- 类型标注可通过 mypy 等静态分析工具与现代 IDE 在运行前捕获类型缺陷,且运行时几乎零性能损耗。 (0:00)
- 从 Python 3.9 开始内置容器直接支持泛型(如 list[int]),typing.List 进入弃用流程;Python 3.10 则引入管道符(如 int | None)简化联合类型书写。 (5:00)
- Python 采用渐进式类型(Gradual Typing)设计,老项目无需重写即可按需逐步添加注解并享受静态检查收益。 (10:00)
Detailed notes
## Background 本视频由“码农高天”于 2024 年 2 月 11 日发布在 YouTube 平台。视频简介指出,本期内容围绕 Python 的 Type Hint(类型标注)展开,系统探讨编写类型标注的原因、核心优势以及最常见的标注实践,旨在帮助开发者更深入地理解并尝试使用这一特性。
## TL;DR 视频从 Python 动态类型在团队协作和大型项目维护中的痛点切入,阐述了类型标注在提升可读性、强化现代 IDE 提示以及配合 mypy 静态分析提前排错的三大优势。接着深入讲解了基础类型、自定义类自引用(字符串注解方案)、容器元素标注(list[int] 与向后兼容的历史演进),以及更推荐的 Sequence 抽象序列用法。随后解析了 Union、Optional 与 Python 3.10 管道符(|)等联合类型的实现形式。最后重点总结了 Python 类型标注所蕴含的“运行时无性能损耗”与“渐进式类型系统”这两大优秀软件工程哲学,证明旧项目可以无负担、平滑地逐步引入类型规范。
## Atmosphere & style 讲解风格清晰透彻、严谨务实,体现出资深工程实践者与开源维护者的从容与洞察力。高天善于通过通俗的比喻(如自引用标注中“先有鸡还是先有蛋”)化解生硬的概念,结合实际开发细节层层递进。整体节奏舒缓自如、富有逻辑美感,不仅传授具体的语法规范,更传递了优雅、温和的工程演化理念,给人一种专业、接地气且极具启发性的视听感受。
## Bulletin - [00:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=0s) Python 自 3.5 起引入 Type Hint,旨在解决大型项目与多人协作中因动态类型造成的代码可读性差及维护成本高问题。 - [00:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=0s) 类型标注可通过 mypy 等静态分析工具与现代 IDE 在运行前捕获类型缺陷,且运行时几乎零性能损耗。 - [05:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=300s) 从 Python 3.9 开始内置容器直接支持泛型(如 list[int]),typing.List 进入弃用流程;Python 3.10 则引入管道符(如 int | None)简化联合类型书写。 - [10:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=600s) Python 采用渐进式类型(Gradual Typing)设计,老项目无需重写即可按需逐步添加注解并享受静态检查收益。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=0s) 动态类型语言在项目规模扩大后容易产生类型错误,Type Hint 能显式展示参数与返回值预期,极大减轻阅读认知负担。 - [00:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=0s) 在函数签名中,使用冒号声明入参类型(如 a: int),使用箭头声明返回值类型(如 -> int)。 - [00:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=0s) 通过终端运行 mypy 或在 VS Code 的 settings.json 中开启 mypy 检查,可以在代码未执行前直接定位类型不匹配错误。 - [05:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=300s) 所有内置基础数据类型(int、str、float 等)及自定义类均可直接作为类型注解,自定义类要求传入其实例对象而非类对象本身。 - [05:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=300s) 在类定义体内自引用自身类型时(如链表节点的前驱引用),使用字符串字面量(如 prev: "Node")可解决尚未定义的“先有鸡还是先有蛋”问题。 - [05:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=300s) 仅标注 list 无法约束容器内部类型,Python 3.9+ 推荐使用内置小写 list[int],若需兼容 3.9 之前版本则需从 typing 导入 List。 - [05:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=300s) 为了让函数接口更通用,接受序列传参时推荐使用 typing.Sequence[int],可同时兼容 list、tuple、range 等对象。 - [05:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=300s) 字典类型需要同时指定键与值的类型,在方括号内用逗号分隔(如 dict[str, int])。 - [10:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=600s) 支持多类型或允许 None 的参数可以使用 typing.Union 或 typing.Optional,二者在表达可选类型时等价。 - [10:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=600s) Python 3.10 引入了管道符语法(如 int | None),取代了繁琐的 typing 导入并提升了代码简洁度。 - [10:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=600s) Type Hint 遵循运行时无惩罚(No Runtime Penalty)原则,增加类型标注几乎不会对程序执行速度造成负面影响。 - [10:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=600s) 渐进式类型系统允许开发者在新代码或核心模块中逐步采用类型标注,无需对遗留代码进行一次性全部重构。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=0s) 为什么要在 Python 中写类型标注(Type Hint)?
Python 作为一种动态类型语言,变量的类型是在运行时(runtime)决定的。这种灵活性虽然让早期的编码速度变得非常快,但随着项目代码量不断膨胀,类型不匹配引发的错误会逐渐增加,给团队协作和大型项目维护带来了明显的效率损耗。码农高天指出:“尤其在你跟其他人合作的时候,有时候你写的程序他可能一眼看不明白这个变量到底是什么东西。”为了解决这一痛点,Python 自 3.5 版本起逐步引入了 Type Hint(类型标注/类型提示),允许开发者以可选的方式显式标注变量和函数签名的类型。
以一个最基本的求和函数 `def f(a, b): return a + b` 为例,原始代码无法让人一眼看出入参和返回值究竟是什么。通过引入标注语法,将其改写为: ```python def f(a: int, b: int) -> int: return a + b ``` 参数名后跟冒号与类型(如 `a: int`)代表输入变量的期望类型,函数参数列表括号与冒号之间的箭头加类型(`-> int`)则代表返回值类型。这样做带来了三大核心好处:
1. **增强代码可读性**:在维护遗留代码或阅读他人代码时,光靠阅读实现往往很难确认一个变量究竟是 `list`、`int`、特定对象实例还是有可能为 `None`。类型标注直接将这些上下文呈现出来,省去了反复翻查函数实现的认知负担。 2. **现代编辑器支持**:现代 IDE 与编辑器(如 VS Code)能直接读取类型标注,在开发者调用外部函数或第三方库时自动提示参数与返回类型,无需深入阅读源码。 3. **提前暴露逻辑错误**:如果传入了一个 `str` 和一个 `int`,在纯动态环境下只能等到运行时才能抛错。而配合静态分析工具(Static Analysis Tool),开发者在运行代码前就能定位类型缺陷。
在 Python 生态中,最主流的静态类型检查工具是 `mypy`。在环境中通过 `pip install mypy` 安装后,只需执行 `mypy example.py` 即可对文件进行静态检查。如果函数期望 `int` 却传入了字符串,`mypy` 会直接在终端指出具体行数与参数类型不符的问题。若使用 VS Code,可以通过按 `Ctrl+Shift+P` 打开设置文件 `settings.json`,配置 `"python.linting.mypyEnabled": true`,从而将类型错误直接以波浪线的形式标红在编辑器窗口内,实现编码阶段的实时纠错。
```mermaid flowchart LR accTitle: Static Analysis Flow accDescr: A diagram showing how static analysis tools check Python source code without running it. subgraph Code["编写阶段"] A["Python 源代码<br/>(带 Type Hint)"] end subgraph Tool["静态分析"] B["mypy 检查器"] C["VS Code 实时反馈"] end subgraph Result["执行结果"] D["提前发现类型错误<br/>(无需运行)"] end A --> B A --> C B --> D C --> D ```
---
[05:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=300s) 常用类型标注:内置类型、自定义类与容器(Container)
在日常编码中,Python 所有内置的基础数据类型(如 `int`、`float`、`str`)都可以直接作为类型标注使用。对于自定义类型,类名本身也可以作为类型注解。例如定义了 `class A`,当编写 `def get_name(item: A) -> str` 时,入参必须是类 `A` 的实例对象(Instance),若直接传入类对象本身 `A`(其类型为 `type A`)或是其他基本类型,`mypy` 都会抛出类型错误。
自定义类标注中存在一个特殊的经典问题:当在类的方法中需要将参数类型标注为该类本身时(例如实现双向链表时,`Node` 类的 `__init__` 初始化方法需要传入前驱节点 `prev: Node`),静态检查器通常会报错提示 `Node` 尚未定义。码农高天将这一现象形容为“先有鸡还是先有蛋的问题”:在类的定义体内部,当前类对象还没有创建完毕,无法在局部作用域中解析该名称。针对这种循环依赖或未就绪的引用,规范的解决方案是将类型名称用引号包裹起来变成字符串字面量(例如 `prev: "Node"`)。这种延后求值(deferred evaluation)的字符串形式完全合法,并受到 `mypy` 的正式支持。
对于容器类型的标注,直接标注 `lst: list` 仅能限定它是一个列表,但无法约束列表内部元素的类型;此时即便在列表中混入非预期类型,`mypy` 也无法捕获。因此,更准确的做法是约束其内部元素的类型: - **容器元素标注**:在 Python 3.9 及以后的版本中,可以直接在内置容器后加方括号指明内部类型,如 `list[int]` 表示包含纯整数的列表。 - **历史版本兼容性与弃用策略**:在 Python 3.9 之前,标准 `list` 并不支持中括号类型参数索引,开发者必须从内置库 `typing` 中引入大写的 `List`(即 `from typing import List`)。然而从 Python 3.9 开始,`typing.List` 已被标记为 Deprecated(已废弃),按照官方规划将在该特性发布后的 5 年逐步从标准库中移除。因此,如果项目需要向前兼容 3.9 以前的 Python 版本(如高天维护的 VizTracer 项目),需要继续使用 `typing.List`;若项目仅支持 3.9+,则应当直接采用内置小写的 `list[int]`。
在实际函数接口设计中,直接要求参数为 `list` 往往过于严苛,因为调用方常常会传入元组(`tuple`)等其他有序序列。若标注为 `list[int]` 却传入 `(1, 2, 3)`,`mypy` 会直接判定类型不匹配。更地道、更通用的做法是采用更抽象的序列类型: ```python from typing import Sequence
def sum_all(lst: Sequence[int]) -> int: return sum(lst) ``` 使用 `Sequence[int]` 作为参数注解,不仅兼容 `list` 和 `tuple`,还可以无缝接受 `range` 对象甚至 `bytes`。
字典类型则需要同时标注键(Key)和值(Value)的类型,并在方括号内以逗号隔开,例如 `dict[str, int]` 表示键为字符串、值为整数的字典映射。
---
[10:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=600s) 多重类型、None 值处理与 Type Hint 的工程哲学
在实际业务逻辑中,一个参数常常允许接受多种可能的类型,最典型的一种模式是传入某个实体类型或传入 `None` 来表示默认/缺省参数(例如 `if x is None:... else:...`)。为了覆盖多类型传参场景,Python 提供了联合类型工具: - **`Union`**:通过 `from typing import Union` 引入,例如 `x: Union[int, None]` 表示参数既可以是整数也可以是 `None`。`Union` 的方括号内可以传入两个或两个以上的任意类型。 - **管道符 `|`**:从 Python 3.10 开始,Python 引入了类似 C 语言位或运算符的语法糖,支持直接写成 `int | None`,大大简化了书写(在 3.9 或更早版本中使用该语法会触发语法错误)。 - **`Optional`**:由于“可能是某类型或可能为 `None`”的模式极其常见,标准库还提供了更具语义表达力的 `Optional`(即 `Optional[int]`,等价于 `Union[int, None]`)。
在视频结尾,码农高天分享了 Python 在推广类型标注时所展现出的两项优秀软件工程理念:
1. **运行时无惩罚(No Runtime Penalty)**:类型标注在 Python 运行时几乎不产生任何性能损耗,程序不会因为增加了详尽的类型注解而变慢。码农高天强调:“这是一个非常好的工程理念,就是我引入一些新的 feature 不会让我这个东西变得更差。” 2. **渐进式类型系统(Gradual Typing)**:开发者不需要在非黑即白之间做选择,现有的老项目完全不需要为了引入 Type Hint 而一次性重写所有代码。你可以在函数中逐步、渐进地加入标注——加了类型标注的部分,静态检查工具会协助分析排错;没有加的地方,Python 依旧当作常规动态类型代码正常解释执行。这种渐进特性让工程团队可以在“无伤”的前提下逐步尝试,感受静态类型检查带来的收益,并平滑地将旧工程迁移到更规范的代码体系中。
- **`Union[int, None]` 与 `Optional[int]`**:二者完全等价,均指代值可以是 `int` 或 `None`。 - **Python 3.10 语法糖**:`int | None` 直接替代繁琐的 `Union` 导入与写法。 - **双向链表类型标注示例**:在节点类声明自身引用时,写为 `prev: "Node"` 解决名称未定义与自引用解析问题。
## Notable quotes > [10:00](https://www.youtube.com/watch?v=HYE85bqNoGw&t=600s) 这是一个非常好的工程理念,就是我引入一些新的 feature 不会让我这个东西变得更差。 — 码农高天
## Mentioned Python, mypy, VS Code, VizTracer, 码农高天
## Video description 这期视频聊一下type hint,也就是类型标注。我们聊一下为什么要做这件事,有什么好处,然后再说一下最常见的类型标注要怎么做。希望大家对这个python家庭新的功能有一个更深入的认识,并且开始尝试使用它。
[Source](https://www.youtube.com/watch?v=HYE85bqNoGw)