|
| 1 | +# Paddle 中的类型提示与 Q&A |
| 2 | + |
| 3 | +Python 在 3.5 版本通过 [PEP 484 – Type Hints](https://peps.python.org/pep-0484) 正式规范了 `类型提示` 功能,以帮助开发者提高代码质量。Paddle 推荐开发者使用此特性,并且将静态类型检查工具 (如 `mypy` ) 集成在 CI 流水线中,以保证基础的类型标注的准确性。但是,由于 Paddle 中存在较多非公开 API 与 c++ 接口,目前版本 (2.6.0) 不声明 Paddle 具有类型标注的完备性 |
| 4 | + |
| 5 | +## Paddle 中的类型提示 |
| 6 | + |
| 7 | +Paddle 中的 `类型提示` 主要关注以下几个部分: |
| 8 | + |
| 9 | +- 函数输入参数的类型 |
| 10 | +- 函数输出参数的类型 |
| 11 | + |
| 12 | +以如下函数为例: |
| 13 | + |
| 14 | +``` python |
| 15 | +def greeting( |
| 16 | + name: str # (1) |
| 17 | +) -> str: # (2) |
| 18 | + """ |
| 19 | + Say hello to your friend! |
| 20 | +
|
| 21 | + Args: |
| 22 | + name (str): The name of your friend. # (3) |
| 23 | +
|
| 24 | + Returns: |
| 25 | + str, The greeting. # (4) |
| 26 | + """ |
| 27 | + return 'Hello ' + name |
| 28 | +``` |
| 29 | + |
| 30 | +其中: |
| 31 | + |
| 32 | +- (1) 函数输入参数的类型 |
| 33 | +- (2) 函数输出参数的类型 |
| 34 | +- (3) 函数文档中输入参数的类型 |
| 35 | +- (4) 函数文档中输出参数的类型 |
| 36 | + |
| 37 | +其中 (1) 和 (3) ,以及 (2) 和 (4) 需要一一对应。 |
| 38 | + |
| 39 | +## Paddle 中类型提示的实现方案 |
| 40 | + |
| 41 | +Python 的类型标注可以通过不同的方式实现,参考 [PEP 561 – Distributing and Packaging Type Information](https://peps.python.org/pep-0561/#implementation) : |
| 42 | + |
| 43 | +> - The package maintainer would like to add type information inline. |
| 44 | +> - The package maintainer would like to add type information via stubs. |
| 45 | +> - A third party or package maintainer would like to share stub files for a package, but the maintainer does not want to include them in the source of the package. |
| 46 | +
|
| 47 | +Paddle 采用 `Inline type annotation + Stub files in package` 的方案,即: |
| 48 | + |
| 49 | +- Python 接口,使用 `inline` 方式标注,如: |
| 50 | + |
| 51 | + ``` python |
| 52 | + def greeting(name): |
| 53 | + return 'Hello ' + name |
| 54 | + ``` |
| 55 | + |
| 56 | + 需要标注为: |
| 57 | + |
| 58 | + ``` python |
| 59 | + def greeting(name: str) -> str: |
| 60 | + return 'Hello ' + name |
| 61 | + ``` |
| 62 | + |
| 63 | +- 非 Python 接口,提供 `stub` 标注文件,如: |
| 64 | + |
| 65 | + 存在一个 c++ 实现的模块 |
| 66 | + |
| 67 | + ``` shell |
| 68 | + foo |
| 69 | + └── bar.py |
| 70 | + ``` |
| 71 | + |
| 72 | + 则应在同一个文件夹下添加 `stub` 文件 `bar.pyi` |
| 73 | + |
| 74 | + ``` shell |
| 75 | + foo |
| 76 | + ├── bar.py |
| 77 | + └── bar.pyi |
| 78 | + ``` |
| 79 | + |
| 80 | + `stub` 文件不需要实现具体的代码逻辑,只需要保留函数定义,具体可以参考 [PEP 561 – Distributing and Packaging Type Information](https://peps.python.org/pep-0561/#implementation) |
| 81 | + |
| 82 | +Python 目前 (`3.12` 版本) 已经完成的相关 `PEP` 有 `21` 个,具体的实现方案可参考 [Typing PEPs](https://peps.python.org/topic/typing/) 。 |
| 83 | + |
| 84 | +## Q&A |
| 85 | + |
| 86 | +### **问:** 我该如何下手? |
| 87 | + |
| 88 | +答:Python 的类型标注特性一直在完善,目前已经是个相对庞大的体系了。 |
| 89 | + |
| 90 | +可以先学习一下 Python 官方的文档:[Static Typing with Python](https://typing.readthedocs.io/en/latest/),熟悉一下相关的 PEP 。 |
| 91 | + |
| 92 | +以 `通过 CI 检查` 作为最基础的实现目标。 |
| 93 | + |
| 94 | +另外,目前 Paddle 添加了 `_typing` 模块,对于一些常用的公用类型做了统一整理,如: |
| 95 | + |
| 96 | +``` pyton |
| 97 | +# python/paddle/_typing/layout.py |
| 98 | +DataLayout2D: TypeAlias = Literal["NCHW", "NHCW"] |
| 99 | +DataLayout3D: TypeAlias = Literal["NCDHW", "NDHWC"] |
| 100 | +``` |
| 101 | + |
| 102 | +标注时应尽量使用 `_typing` 模块中的类型,以方便后续维护。 |
| 103 | + |
| 104 | +### **问:** docstring 中的 Args 与 type annotation 有什么区别? |
| 105 | + |
| 106 | +答:Paddle 之前的版本 (2.6.0 及以前) 未统一进行类型标注,而在 docstring 中描述了参数类型。 |
| 107 | +docstring 中 Args 的参数类型以方便用户理解为目的,在与 type annotation 不冲突的前提下,可以保持简洁。如: |
| 108 | + |
| 109 | +``` python |
| 110 | +def test(a: int | list[int] | tuple[int, ...]) -> None: |
| 111 | + """ |
| 112 | + ... |
| 113 | +
|
| 114 | + Args: |
| 115 | + a (int|list|tuple): xxx |
| 116 | +
|
| 117 | + Returns: |
| 118 | + None, xxx |
| 119 | +
|
| 120 | + ... |
| 121 | + """ |
| 122 | +``` |
| 123 | + |
| 124 | +### **问:** docstring 中的 Args 与 type annotation 不一致怎么办? |
| 125 | + |
| 126 | +答:首先需要保证 type annotation 的正确性,如果 docstring 原有 Args 中的类型不正确,需要进行修改,并且,同时检查此接口的 `中文文档` (即 `docs`)是否正确,如发现错误,需要对 `docs` 单独提 PR 进行修改。 |
| 127 | + |
| 128 | +### **问:** 该使用 `Union` 还是 `|` 以及 `from __future__ import annotations` ? |
| 129 | + |
| 130 | +答:尽可能的使用 `|` ,通常需要 `from __future__ import annotations` 。 |
| 131 | + |
| 132 | +由于目前 Paddle (2.6.0) 支持的 Python 最低版本为 `3.8` ,因此,`|` 只能在类型标注的情况下使用,而不能在表达式中使用,并且,同时需要 `from __future__ import annotations`,如: |
| 133 | + |
| 134 | +``` python |
| 135 | +from __future__ import annotations |
| 136 | +def test(a: int | str): ... |
| 137 | +``` |
| 138 | + |
| 139 | +而在表达式中仍使用 `Union` : |
| 140 | + |
| 141 | +``` python |
| 142 | +from typing import Union |
| 143 | +t = Union[int, str] |
| 144 | +``` |
| 145 | + |
| 146 | +可参考 [PEP 563 – Postponed Evaluation of Annotations](https://peps.python.org/pep-0563/) 。 |
| 147 | + |
| 148 | +### **问:** 如果测试无法通过怎么办? |
| 149 | + |
| 150 | +答:可以使用 `# type: ignore` 进行规避。 |
| 151 | + |
| 152 | +Paddle 通过工具 (如 `mypy`) 对接口的示例代码进行检查,进而保证类型标注的正确性。 |
| 153 | + |
| 154 | +类型标注的过程中,难免产生接口依赖问题,如果依赖的是 `私有接口` 或 `外部接口` ,则可以使用 `# type: ignore` 规避相应的类型检查,如: |
| 155 | + |
| 156 | +``` python |
| 157 | +>>> import abcde # type: ignore |
| 158 | +>>> print('ok') |
| 159 | +``` |
| 160 | + |
| 161 | +或者规避整个代码检查: |
| 162 | + |
| 163 | +``` python |
| 164 | +>>> # type: ignore |
| 165 | +>>> import abcde |
| 166 | +>>> print('ok') |
| 167 | +``` |
| 168 | + |
| 169 | +### **问:** 能否使用 `Any` 类型? |
| 170 | + |
| 171 | +答:可以,但应尽量避免。 |
| 172 | + |
| 173 | +### **问:** 如果出现 `circular import` 错误怎么办? |
| 174 | + |
| 175 | +答:出现此情况可以参考以下处理方法: |
| 176 | + |
| 177 | +- 添加 `from __future__ import annotations` |
| 178 | +- 将类型单独通过 `typing.TYPE_CHECKING` 引入,如: |
| 179 | + |
| 180 | + ``` python |
| 181 | + from typing import TYPE_CHECKING |
| 182 | + if TYPE_CHECKING: |
| 183 | + import paddle.xxx as xxx |
| 184 | + |
| 185 | + def tmp() -> xxx: ... |
| 186 | + ``` |
| 187 | + |
| 188 | + 另外,如果标注的类型仅用作 type hints,也尽可能的使用 `TYPE_CHECKING` ,以减少不必要的模块导入。 |
| 189 | + |
| 190 | +### **问:** 使用 `Tensor` 还是 `Variable`? |
| 191 | + |
| 192 | +答:尽量使用 `Tensor` ,不将静态图的 `Variable/Value` 概念暴露给用户。 |
| 193 | + |
| 194 | +更详细的讨论可以参考 https://github.com/PaddlePaddle/community/pull/858#discussion_r1564552690 |
| 195 | + |
| 196 | +### **问:** 如果遇到需要根据不同输入类型有不同输出类型的函数怎么办? |
| 197 | + |
| 198 | +答:出现此情况可以参考以下处理方法: |
| 199 | + |
| 200 | +- 添加 `from typing import overload` |
| 201 | +- 标注多个同名函数,并用装饰器装饰,如: |
| 202 | + |
| 203 | + ``` python |
| 204 | + from typing import overload |
| 205 | + |
| 206 | + @overload |
| 207 | + def array_length(array: list[Any]) -> int:... |
| 208 | + |
| 209 | + @overload |
| 210 | + def array_length(array: paddle.Tensor) -> paddle.Tensor:... |
| 211 | + |
| 212 | + def array_length(array): ... # 具体实现的代码,不再进行标注 |
| 213 | + ``` |
| 214 | + |
| 215 | +### **问:** 什么时候用 `Sequence` ,什么时候用 `list` 和 `tuple`? |
| 216 | + |
| 217 | +答:Python 的 PEP 中有提示: |
| 218 | + |
| 219 | +> Note: Dict, DefaultDict, List, Set and FrozenSet are mainly useful for annotating return values. For arguments, prefer the abstract collection types defined below, e.g. Mapping, Sequence or AbstractSet. |
| 220 | + |
| 221 | +也就是说,输入中用 `Sequence` ,返回值用 `list` 。 |
| 222 | + |
| 223 | +但是,如果代码中使用到了 `list` 的方法,如 `append` ,或者明确表示此输入只能是 `list` ,则不应再使用 `Sequence` 。 |
| 224 | + |
| 225 | +### **问:** 标注的时候用 `Tensor` 还是 `paddle.Tensor`? |
| 226 | + |
| 227 | +答:两者皆可。 |
| 228 | + |
| 229 | +若文件中出现较多 `paddle.Tensor` ,出于简洁的考虑,可以使用 `Tensor` 代替,但是需要在导入包时注意: |
| 230 | + |
| 231 | +``` python |
| 232 | +if TYPE_CHECKING: |
| 233 | + from paddle import Tensor |
| 234 | +``` |
| 235 | + |
| 236 | +可参考讨论:https://github.com/PaddlePaddle/Paddle/pull/65073#discussion_r1636116450 |
| 237 | + |
| 238 | +### **问:** 该用 `paddle.framework.Block` 还是 `paddle.pir.Block`? |
| 239 | + |
| 240 | +答:统一使用 `paddle.pir.Block`。 |
| 241 | + |
| 242 | +可参考讨论:https://github.com/PaddlePaddle/Paddle/pull/65095#discussion_r1637570850 |
| 243 | + |
| 244 | +## 参考资料 |
| 245 | + |
| 246 | +- [PEP 484 – Type Hints](https://peps.python.org/pep-0484/) |
| 247 | +- [PEP 563 – Postponed Evaluation of Annotations](https://peps.python.org/pep-0563/) |
| 248 | +- [PEP 561 – Distributing and Packaging Type Information](https://peps.python.org/pep-0561/#implementation) |
| 249 | +- [Typing PEPs](https://peps.python.org/topic/typing/) |
| 250 | +- [Static Typing with Python](https://typing.readthedocs.io/en/latest/index.html#) |
0 commit comments