Python 文档示例代码书写规范

书写规范

规范概要:

  1. 第 1 节,显式的输出优于注释

  2. 第 2 节,固定的输出优于随机

  3. 第 3 节,明确的设备优于默认

  4. 第 4 节,尝试去检查优于跳过

执行说明:

  1. 规范在执行过程中,可能会发现现有规范未考虑到的方面,需要在实施过程中不断补充与完善,也请大家积极反馈意见。

  2. 示例代码的执行限时为 10 秒,即,要求示例代码在 10 秒内执行完毕。如有特殊情况,如网络下载等情况,可以向 reviewer 提出增加 TIMEOUT 指令。

  3. 如遇其他问题,如符合规范的示例代码无法进行检查等情况,请及时反馈 reviewer 进行确认。

显式的输出优于注释

请尽量将用户可能关注的输出,如变量的值、Tensorshape 等,书写或拷贝至示例中。

可以使用 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]])

如果示例中涉及 PythonNumpy 随机数等情况,也需要进行随机种子的设置。

明确的设备优于默认

由于 TensorCPUGPU 等设备上面的行为可能不同,请写明具体的设备需求。

如:

>>> # 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') # 可以不写

另外,对于有 GPUXPU 等设备需求的情况,需要在代码开头添加 doctest 指令,如:

  • 需要 GPU 环境

    >>> # doctest: +REQUIRES(env:GPU)
    >>> ...
    
  • 需要 XPU 环境

    >>> # doctest: +REQUIRES(env:XPU)
    >>> ...
    
  • 需要 GPUXPU 等多个环境

    >>> # doctest: +REQUIRES(env:GPU, env:XPU)
    >>> ...
    

请注意这里的 大小写 ,其中 doctest 为小写,REQUIRES 为大写,env 为小写,GPU 为大写。

尝试去检查优于跳过

示例代码的检查可以保证其正确性,但并不是所有代码均能够正常或正确的在 CI 环境中运行。

如:

  • 依赖外部资源才能运行,如 imageaudio 等文件。

  • 代码的随机性由系统决定,如 os.walk 等方法。

  • 代码不能在 CI 中正常运行,如 inspect.getsourcelines 会抛出 raise OSError('could not get source code')

等情况,此时,可以使用 doctestSKIP 指令跳过检查,如:

>>> # 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 是一个庞大且复杂的框架,涉及较多全局变量,以及动态图与静态图转换等操作,客观上要求每次示例代码的运行保持干净的初始化状态

  • 示例代码检查与单元测试不同,一般不会进行复杂的运行前后处理(如 setUptearDown),这就导致了单个示例运行之后,可能污染检查环境

  • Paddle 内部的一些接口,如异步 io 等操作,也会涉及到多进程操作,这就要求示例检查工具不能够影响 Paddle 自身的进程管理

基于此,示例检查工具遵循以下设计:

  • 示例检查工具在每次运行检查前,都会单独创建进程,并在进程中 import paddle,以初始化环境

  • 示例检查工具使用 pythonmultiprocessing 开辟进程,基于安全性的考虑(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 的示例,需要事先确认各个示例间不会相会影响。