Python 文档示例代码书写规范¶
书写规范¶
规范概要:
第 1 节,显式的输出优于注释
第 2 节,固定的输出优于随机
第 3 节,明确的设备优于默认
第 4 节,尝试去检查优于跳过
执行说明:
规范在执行过程中,可能会发现现有规范未考虑到的方面,需要在实施过程中不断补充与完善,也请大家积极反馈意见。
示例代码的执行限时为
10
秒,即,要求示例代码在10
秒内执行完毕。如有特殊情况,如网络下载等情况,可以向 reviewer 提出增加TIMEOUT
指令。如遇其他问题,如符合规范的示例代码无法进行检查等情况,请及时反馈 reviewer 进行确认。
显式的输出优于注释¶
请尽量将用户可能关注的输出,如变量的值、Tensor
的 shape
等,书写或拷贝至示例中。
可以使用 print
输出结果,如:
>>> import paddle
>>> x = paddle.to_tensor([[1, 2], [3, 4]])
>>> y = paddle.to_tensor([[5, 6], [7, 8]])
>>> res = paddle.multiply(x, y)
>>> print(res)
Tensor(shape=[2, 2], dtype=int64, place=Place(cpu), stop_gradient=True,
[[5 , 12],
[21, 32]])
如无特殊情况,请勿使用 #
的注释方式提供输出值。
如:
>>> res = paddle.multiply(x, y) # shape=[2, 2]
>>> res = paddle.multiply(x, y)
>>> # [[5 , 12],
>>> # [21, 32]]
等,都是不建议的输出方式。
另外,在书写或拷贝示例代码时,请注意以下几点:
输出中请 不要 留有空行。
如:
>>> import paddle >>> x = paddle.to_tensor([[1, 2], [3, 4]]) >>> y = paddle.to_tensor([[5, 6], [7, 8]]) >>> res = paddle.multiply(x, y) >>> print(res) Tensor(shape=[2, 2], dtype=int64, place=Place(cpu), stop_gradient=True, [[5 , 12], [21, 32]])
请改为:
>>> import paddle >>> x = paddle.to_tensor([[1, 2], [3, 4]]) >>> y = paddle.to_tensor([[5, 6], [7, 8]]) >>> res = paddle.multiply(x, y) >>> print(res) Tensor(shape=[2, 2], dtype=int64, place=Place(cpu), stop_gradient=True, [[5 , 12], [21, 32]])
对于代码中的多行或复合语句,也 不要 留有空行。
如:
>>> class Mnist(nn.Layer): ... def __init__(self): ... super().__init__() ...
请改为:
>>> class Mnist(nn.Layer): ... def __init__(self): ... ... super().__init__() ...
或者:
>>> class Mnist(nn.Layer): ... def __init__(self): ... super().__init__() ...
输出请统一 左对齐 其上方的
>>>
或...
。如:
>>> import paddle >>> x = paddle.to_tensor([[1, 2], [3, 4]]) >>> y = paddle.to_tensor([[5, 6], [7, 8]]) >>> res = paddle.multiply(x, y) >>> print(res) Tensor(shape=[2, 2], dtype=int64, place=Place(cpu), stop_gradient=True, [[5 , 12], [21, 32]])
请改为:
>>> import paddle >>> x = paddle.to_tensor([[1, 2], [3, 4]]) >>> y = paddle.to_tensor([[5, 6], [7, 8]]) >>> res = paddle.multiply(x, y) >>> print(res) Tensor(shape=[2, 2], dtype=int64, place=Place(cpu), stop_gradient=True, [[5 , 12], [21, 32]])
适度的空格用于美化输出是允许的。
如:
>>> import paddle >>> x = paddle.to_tensor([[1, 2], [3, 4]]) >>> y = paddle.to_tensor([[5, 6], [7, 8]]) >>> res = paddle.multiply(x, y) >>> print(res) Tensor(shape=[2, 2], dtype=int64, place=Place(cpu), stop_gradient=True, [[5 , 12], [21, 32]])
可以对齐其中的方括号为:
>>> import paddle >>> x = paddle.to_tensor([[1, 2], [3, 4]]) >>> y = paddle.to_tensor([[5, 6], [7, 8]]) >>> res = paddle.multiply(x, y) >>> print(res) Tensor(shape=[2, 2], dtype=int64, place=Place(cpu), stop_gradient=True, [[5 , 12], [21, 32]])
对于多行的输出,可以使用
...
作为通配符使用。如:
>>> sampler = MySampler(data_source=RandomDataset(100)) >>> for index in sampler: ... print(index) 0 1 2 ... 99
固定的输出优于随机¶
请尽量保证输出为固定值,对于示例中的随机情况,请设置随机种子。
如:
>>> import paddle
>>> paddle.seed(2023)
>>> data = paddle.rand(shape=[2, 3])
>>> print(data)
Tensor(shape=[2, 3], dtype=float32, place=Place(cpu), stop_gradient=True,
[[0.86583614, 0.52014720, 0.25960937],
[0.90525323, 0.42400089, 0.40641287]])
如果示例中涉及 Python
、Numpy
随机数等情况,也需要进行随机种子的设置。
明确的设备优于默认¶
由于 Tensor
在 CPU
、GPU
等设备上面的行为可能不同,请写明具体的设备需求。
如:
>>> # doctest: +REQUIRES(env:GPU)
>>> import paddle
>>> paddle.device.set_device('gpu')
>>> count = paddle.device.cuda.device_count()
>>> print(count)
1
其中:
第
1
行是doctest
检查指令。第
3
行是设备的设置。
CI 中默认使用 CPU
作为检查环境,因此,如无特殊情况,可以 不用 写明 CPU
环境,如:
>>> paddle.device.set_device('cpu') # 可以不写
另外,对于有 GPU
、XPU
等设备需求的情况,需要在代码开头添加 doctest
指令,如:
需要
GPU
环境>>> # doctest: +REQUIRES(env:GPU) >>> ...
需要
XPU
环境>>> # doctest: +REQUIRES(env:XPU) >>> ...
需要
GPU
、XPU
等多个环境>>> # doctest: +REQUIRES(env:GPU, env:XPU) >>> ...
请注意这里的 大小写 ,其中 doctest
为小写,REQUIRES
为大写,env
为小写,GPU
为大写。
尝试去检查优于跳过¶
示例代码的检查可以保证其正确性,但并不是所有代码均能够正常或正确的在 CI 环境中运行。
如:
依赖外部资源才能运行,如
image
、audio
等文件。代码的随机性由系统决定,如 os.walk 等方法。
代码不能在 CI 中正常运行,如
inspect.getsourcelines
会抛出raise OSError('could not get source code')
等情况,此时,可以使用 doctest
的 SKIP
指令跳过检查,如:
>>> # doctest: +SKIP('file not exist')
>>> with open('cat.jpg') as f:
... im = load_image_bytes(f.read())
SKIP
指令还可以成对使用,如:
>>> # doctest: +SKIP('file not exist')
>>> with open('cat.jpg') as f:
... im = load_image_bytes(f.read())
>>> # doctest: -SKIP
>>> x = paddle.to_tensor([[1, 2], [3, 4]])
>>> ...
其中,
+SKIP
表示后面的代码要跳过,-SKIP
表示恢复检查。+SKIP
可以加上说明,如+SKIP('file not exist')
特殊指令¶
TIMEOUT 指令¶
指令格式¶
directive ::= "# doctest: +TIMEOUT(" time ")"
time ::= nonzerodigit (digit)*
nonzerodigit ::= "1"..."9"
digit ::= "0"..."9"
指令说明¶
示例代码检查的默认限制时间为 10
秒,但有些情况下代码无法及时完成运行,如,需要通过网络下载较大的模型或数据集。如果出现此类情况,开发者可以与 reviewer 协商增加 TIMEOUT
指令,如:
>>> from paddle.vision.datasets import VOC2012
>>> voc2012 = VOC2012()
此段示例代码需要通过网络进行下载,运行时间超过了限制的执行时间 10
秒,此时,在 reviewer 同意的情况下,可以添加 TIMEOUT
指令,修改为:
>>> # doctest: +TIMEOUT(60)
>>> from paddle.vision.datasets import VOC2012
>>> voc2012 = VOC2012()
此时,此段示例代码的执行限时被修改为 60
秒。
TIMEOUT
的具体时长可根据实际情况进行修改。
SOLO 指令¶
指令格式¶
directive ::= "# doctest: +SOLO(" reason ")"
reason ::= "'" shortstringchar+ "'" | '"' shortstringchar+ '"'
shortstringchar ::= <any source character except "\" or newline or the quote>
指令说明¶
SOLO
多指音乐中的 独奏
,在示例代码的写作中,用以指代 Main Process Separately
,即,指示示例检查工具使用 主进程单独
运行此示例代码。
示例检查工具使用了 python
中的 multiprocessing
模块,虽然 multiprocessing
多应用于多进程加速,但,在示例检查中,主要用作隔离各个示例代码的检查环境,这有几个原因:
Paddle
是一个庞大且复杂的框架,涉及较多全局变量,以及动态图与静态图转换等操作,客观上要求每次示例代码的运行保持干净的初始化状态示例代码检查与单元测试不同,一般不会进行复杂的运行前后处理(如
setUp
、tearDown
),这就导致了单个示例运行之后,可能污染检查环境Paddle
内部的一些接口,如异步io
等操作,也会涉及到多进程操作,这就要求示例检查工具不能够影响Paddle
自身的进程管理
基于此,示例检查工具遵循以下设计:
示例检查工具在每次运行检查前,都会单独创建进程,并在进程中
import paddle
,以初始化环境示例检查工具使用
python
的multiprocessing
开辟进程,基于安全性的考虑(Contexts and start methods),使用spawn
的方式由于多进程嵌套可能存在问题,如示例中使用异步
io
又开辟多个进程,特此引入SOLO
指令,指示示例检查工具,此示例代码需要在主进程中单独运行
以一个简单的多进程代码为例:
>>> import multiprocessing
>>> p = multiprocessing.Process(
... target=lambda a, b: a + b,
... args=(1, 2),
... )
>>> p.start()
>>> p.join()
在 reviewer 同意的情况下,可以添加 SOLO
指令,修改为:
>>> # doctest: +SOLO('can not use add in multiprocessing')
>>> import multiprocessing
>>> p = multiprocessing.Process(
... target=lambda a, b: a + b,
... args=(1, 2),
... )
>>> p.start()
>>> p.join()
此时,示例单独在主进程中运行检查,并能正常通过。
注意: 使用 SOLO
指令时,由于主进程环境没有特殊的运行前和运行后处理,因此,如果一次运行多个 SOLO
的示例,需要事先确认各个示例间不会相会影响。