Skip to content

Commit f998df4

Browse files
authored
[Typing] 增加文档《Paddle 中的类型提示与 Q&A》 (#6678)
* [Add] code typing writing spec * [Update] index_cn.rst
1 parent 763c284 commit f998df4

File tree

5 files changed

+255
-4
lines changed

5 files changed

+255
-4
lines changed
341 KB
Loading
Binary file not shown.

docs/dev_guides/api_contributing_guides/new_python_api_cn.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ API 作为用户使用飞桨框架的接口,承接着实现用户模型开发
5656

5757
先看一个简单的 Python API 的代码样例,如图 1 所示,可以看到主要包括以下几部分:
5858

59-
- **函数定义**:定义 Python 接口函数
59+
- **函数定义**:定义 Python 接口函数与参数类型
6060
- **英文文档**:API 的英文文档直接写在 `.py` 代码文件中,如下图所示;API 的中文文档则写到 [PaddlePaddle/docs](https://github.com/PaddlePaddle/docs) 仓库中。
6161
- **代码示例**:该 API 的使用示例代码。
6262
- **函数主体代码**:包括输入参数的检查、调用算子的执行逻辑等内容。
6363

64-
<center><img src="https://github.com/PaddlePaddle/docs/blob/develop/docs/dev_guides/api_contributing_guides/images/zeros_python_api_doctest.png?raw=true" width="900px" ></center>
64+
<center><img src="https://github.com/PaddlePaddle/docs/blob/develop/docs/dev_guides/api_contributing_guides/images/zeros_python_api.png?raw=true" width="900px" ></center>
6565

6666
<center>图 1 Python API 代码样例</center>
6767

@@ -75,7 +75,7 @@ API 作为用户使用飞桨框架的接口,承接着实现用户模型开发
7575

7676
```python
7777
def zeros(shape, dtype=None, name=None):
78-
# 为了突出重点,省略中间的文档和示例部分
78+
# 为了突出重点,省略类型标注与中间的文档和示例部分
7979
if dtype is None:
8080
dtype = 'float32'
8181
return fill_constant(value=0.0, shape=shape, dtype=dtype, name=name)
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
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#)

docs/dev_guides/style_guide_and_references/index_cn.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- `代码风格检查指南 <../git_guides/codestyle_check_guide_cn.html>`_ : Paddle 进行代码风格检查相关工具和说明。
1010
- `Paddle CI 测试详解 <../git_guides/paddle_ci_manual_cn.html>`_ : Paddle CI 测试流水线中的测试项的详细介绍,以及 CI 失败的处理方法。
1111
- `Python 文档示例代码书写规范 <./code_example_writing_specification_cn.html>`_ : Python 文档示例代码书写规范。
12-
12+
- `Paddle 中的类型提示与 Q&A <./code_typing_writing_specification_cn.html>`_ : Python 文档示例代码书写规范。
1313

1414
.. toctree::
1515
:hidden:
@@ -19,3 +19,4 @@
1919
../git_guides/codestyle_check_guide_cn.md
2020
../git_guides/paddle_ci_manual_cn.md
2121
code_example_writing_specification_cn.md
22+
code_typing_writing_specification_cn.md

0 commit comments

Comments
 (0)