Python Argument Clinic功能解析
一、Argument Clinic概述
在阅读cpython源码的过程中,常常能在模块上看到如下的注释语句:
1 | /*[clinic input] |
clinic
是什么?本质是其实就是一个python脚本。
clinic
有什么功能?自动生成cpython中参数解析功能的相关代码。
clinic
是cpython中c文件的预处理器,可以通过固定格式的模板为builtins
模块自动生成参数解析代码。如果自己维护cpython的参数解析代码,是一项较为繁琐的工作,需要在大量的地方维护冗余信息。当使用Argument Clinic
功能时,我们不再需要自己进行参数解析,基于Argument Clinic
生成的参数解析代码可以作为一个黑盒使用。
当前cpython中大部分参数解析的函数都使用了Argument Clinic
模板自动生成功能,本文主要为所有打算编写自定义模块和维护现有builtins
的同学提供基础指导。
二、基本语法
2.1 clinic input和output
clinic可以扫描文件中的指定行作为关键字,clinic input行之间的所有内容作为Clinic的模板输入,通常被称为Clinic block
,也是我们需要重点关注和修改的部分。
- clinic input start:
/*[clinic input]
- clinic input end:
[clinic start generated code]*/
构建python后执行命令行:./python .\Tools\clinic\clinic.py foo.c
可以扫描foo.c
文件中的所有clinic block
并生成代码,并在最后加上/*[clinic end generated code: output=xx input=xx]*/
作为校验行,用于验证输入输出的对应关系。下面给出一个简单的例子:
1 | /* foo.c */ |
2.2 创建clinic模板
本节使用python3.10的_pickle.c
模块作为样例,解析clinic的模板格式。
-
首先需要在类顶部声明模块/类定义,类似于C语言常常在文件顶部进行声明。此处应对所有模块与类进行声明,其名称应该与Python界面的名称保持一致,可以使用
PyModuleDef
和PyTypeObject
中定义的名称。_pickle中模块及类的clinic定义样例:
1
2
3
4/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/ -
创建函数块的clinic,应该由几部分组成:
-
模块.类.方法名称(与python保持一致),(可选) 使用
->
在method后添加返回值类型; -
空行后写入参数名称及类型,每个参数都应占独立一行;
-
(可选) 为参数设置默认值,格式为
name_of_parameter: converter = default_value
;converter是什么?
我们需要了解参数应该被转换成什么类型,通常使用单个字符来表示某个特定类型,例如’O’表示对象,'s’表示字符串,'i’表示int型参数;详细可以参考:arg-parsing
-
(可选) 新增一行后缩进,为每个参数添加文档说明;
-
(可选) 若使用
PyArg_ParseTuple()
解析参数,则所有参数都是位置相关,在最后加上/
标记即可。如果需要使用关键字去解析参数PyArg_ParseTupleAndKeywords()
则不需要加/
; -
(可选) 空行后,支持写入方法说明文档;
1
2
3
4
5
6
7
8
9
10/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
argument document(optional)
/
Write a pickled representation of the given object to the open file.
[clinic start generated code]*/ -
2.3 clinc代码生成
这里给出一个_pickle
模块的例子验证代码生成的功能,我们构建python后,执行命令行:
./python .\Tools\clinic\clinic.py .\Modules\_pickle.c
可以看到clinic
其实就是一个生成代码的python脚本。
1 | // 未使用clinic时的原始pickler_dump函数 |
使用clinic处理后发生了几点变化:
- 方法名根据clinic中定义的发生了改变,按模块/类/方法的格式进行定义
_pickle_Pickler_dump
; - 除了固定的self参数,其余参数根据clinic定义自动生成,此处自动生成了
PyObject *obj
; - 参数不再需要传递
PyObject *args
,再使用PyArg_ParseTuple
一个一个解析,解析的过程将自动完成;
1 | /*[clinic input] |
我们再添加一个int型参数来查看变化,可以看到:
- 函数增加了一个int型参数,这和我们预想的一样;
- 函数名变为
_pickle_Pickler_dump_impl
,clinic会在函数名后增加_impl
后缀; - 在
_pickle.h
文件中新增了_pickle_Pickler_dump
,自动补全了参数解析的代码块。
1 | /*[clinic input] |
可以看到使用clinic
后不管是多少参数,都可以自动生成参数解析代码块,用声明式编程取代传统的命令式编程,使得我们的编码过程更加简单。
三、参考文章
除此了基本的功能之外,clinic还提供了强大的高级特性用于通过模板生成各种类型的函数,详细可见如下文档: