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 的示例,需要事先确认各个示例间不会相会影响。