On this page
Arguments 诊所如何
author
- Larry Hastings
Abstract
Argument Clinic 是 CPython C 文件的预处理器。其目的是使所有与编写“ buildins”参数解析代码有关的样板自动化。本文档向您展示如何转换第一个 C 函数以与 Argument Clinic 一起使用,然后介绍有关 Argument Clinic 用法的一些高级主题。
目前,Argument Clinic 仅被视为 CPython 内部使用。 CPython 外部的文件不支持使用它,也不保证将来版本的向后兼容性。换句话说:如果您维护 CPython 的外部 C 扩展,欢迎您在自己的代码中试验 Argument Clinic。但是,下一版 CPython 附带的 Argument Clinic 版本可能会完全不兼容并破坏所有代码。
论据诊所的目标
Argument Clinic 的主要目标是承担 CPython 内部所有参数解析代码的责任。这意味着,当您将函数转换为可与 Argument Clinic 一起使用时,该函数将不再执行其自身的参数解析-Argument Clinic 生成的代码对您来说应该是“黑匣子”,CPython 在顶部,然后在底部调用您的代码,并将PyObject *args
(甚至PyObject *kwargs
)神奇地转换为所需的 C 变量和类型。
为了使 Argument Clinic 达到其主要目标,它必须易于使用。当前,使用 CPython 的参数解析库很麻烦,需要在数量惊人的地方维护冗余信息。使用 Argument Clinic 时,您不必重复自己。
显然,除非解决了他们的问题,并且没有 Creating 自己的新问题,否则没人愿意使用 Argument Clinic。因此,Argument Clinic 生成正确的代码至关重要。如果代码也更快,那将是很好的,但是至少它不应该引入主要的速度回归。 (finally,Argument Clinic 应该大大提高了速度—我们可以重写其代码生成器以生成量身定制的参数解析代码,而不用调用通用的 CPython 参数解析库.这将使最快的参数解析成为可能! )
此外,“参数诊所”必须足够灵活,以使用任何方法进行参数解析。 Python 具有一些具有某些非常奇怪的解析行为的Function;论据诊所的目标是为所有人提供支持。
最后,Argument Clinic 的最初动机是为 CPython 内置函数提供内省的“签名”。过去,如果您传入内置函数,自省查询Function将引发异常。有了 Argument Clinic,这已成为过去!
与 Argument Clinic 一起工作时,您应该牢记一个想法:您提供的信息越多,它就能做得更好。Arguments 诊所目前被认为是相对简单的。但是随着它的 Developing,它将变得更加复杂,并且它应该能够利用您提供的所有信息来完成许多有趣的事情。
基本概念和用法
CPython 自带了 Argument Clinic;您将在Tools/clinic/clinic.py
中找到它。如果运行该脚本,请指定 C 文件作为参数:
$ python3 Tools/clinic/clinic.py foo.c
Argument Clinic 将扫描文件,以查找如下所示的行:
/*[clinic input]
当找到一个时,它将读取所有内容,直到一行看起来完全像这样:
[clinic start generated code]*/
这两行之间的所有内容都 Importing 到 Argument Clinic。所有这些行,包括开始和结尾的 Comments 行,统称为 Argument Clinic“块”。
当 Argument Clinic 解析这些块之一时,它将生成输出。该输出在该块之后立即被重写到 C 文件中,后跟包含校验和的 Comments。 Argument Clinic 块现在看起来像这样:
/*[clinic input]
... clinic input goes here ...
[clinic start generated code]*/
... clinic output goes here ...
/*[clinic end generated code: checksum=...]*/
如果第二次在同一文件上运行 Argument Clinic,则 Argument Clinic 将丢弃旧的输出,并用新的校验和行写出新的输出。但是,如果 Importing 没有更改,则输出也不会更改。
您永远不要修改 Argument Clinic 块的输出部分。而是更改 Importing,直到产生所需的输出。 (这是校验和的目的-检测是否有人更改了输出,因为下次 Argument Clinic 写入新输出时,这些编辑将丢失.)
为了清楚起见,以下是我们将与 Argument Clinic 一起使用的术语:
Comments 的第一行(
/*[clinic input]
)是起始行。初始 Comments 的最后一行(
[clinic start generated code]*/
)是结束行。最后一行(
/*[clinic end generated code: checksum=...]*/
)是* checksum 行*。在起始行和结束行之间是* input *。
在末行和校验和行之间是* output *。
从起始行到校验和行(包括首尾行)的所有文本共同为* block *。 (Argument Clinic 尚未成功处理的一个块却没有输出或校验和行,但仍被视为一个块.)
转换您的第一个Function
理解 Argument Clinic 如何工作的最好方法是将一个函数转换为可使用该函数。然后,这里是将函数转换为与 Argument Clinic 一起使用所需的最低限度的步骤。请注意,对于计划签入 CPython 的代码,您确实应该使用文档中稍后将看到的一些高级概念(例如“返回转换器”和“自转换器”)来进一步推动转换。但是,本演练将使其变得简单,以便您学习。
让我们潜入吧!
确保您正在使用 CPython 干线的最新更新结帐。
查找一个名为PyArg_ParseTuple()或PyArg_ParseTupleAndKeywords()的 Python 内置函数,并且尚未转换为可与 Argument Clinic 一起使用。对于我的示例,我使用
_pickle.Pickler.dump()
。如果对
PyArg_Parse
函数的调用使用以下任何格式单位:
O&
O!
es
es#
et
et#
或者,如果它有多个对PyArg_ParseTuple()的调用,则应选择其他函数。自变量诊所支持所有这些方案。但是这些都是高级主题,让我们为您的第一个Function做一些简单的事情。
另外,如果函数有多个对PyArg_ParseTuple()或PyArg_ParseTupleAndKeywords()的调用,并且该函数支持同一参数的不同类型,或者该函数使用 PyArg_Parse 函数之外的其他函数来解析其参数,则可能不适合转换为 Argument Clinic。 Argument Clinic 不支持通用函数或多态参数。
- 在函数上方添加以下样板,创建我们的块:
/*[clinic input]
[clinic start generated code]*/
- 剪切文档字符串并将其粘贴在
[clinic]
行之间,删除所有使其成为正确引用的 C 字符串的垃圾。完成后,您应该只在左边距处有 Literals,且没有超过 80 个字符的行。 (参数诊所将在文档字符串中保留缩进.)
如果旧文档字符串的第一行看起来像函数签名,则将该行扔掉。 (该文档字符串不再需要它—当将来在内置函数上使用help()
时,第一行将根据函数签名自动构建.)
Sample:
/*[clinic input]
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
- 如果您的文档字符串没有“摘要”行,则 Argument Clinic 将投诉。因此,请确保它具有一个。 “摘要”行应是一个在文档字符串开头由 80 列单行组成的段落。
(我们的示例文档字符串仅包含一个摘要行,因此此步骤无需更改示例代码.)
- 在文档字符串上方,Importing 函数名称,后跟空白行。这应该是该函数的 Python 名称,并且应该是该函数的完整虚线路径-它应以模块名称开头,包括所有子模块,并且如果该函数是类的方法,则应包括类名称也是如此。
Sample:
/*[clinic input]
_pickle.Pickler.dump
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
- 如果这是该模块文件或类第一次与该 C 文件中的 Argument Clinic 一起使用,则必须语句该模块和/或类。正确的论点临床卫生更喜欢在 C 文件顶部附近的一个单独的块中语句它们,就像在顶部包含文件和静态变量一样。 (在示例代码中,我们将仅显示两个块彼此相邻.)
类和模块的名称应与 Python 看到的名称相同。检查相应的在PyModuleDef或PyTypeObject中定义的名称。
当语句一个类时,还必须在 C 中指定其类型的两个方面:用于该类实例的指针的类型语句,以及该类的PyTypeObject的指针。
Sample:
/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
- 为函数语句每个参数。每个参数应具有其自己的行。所有参数行都应从函数名称和文档字符串中缩进。
这些参数行的一般形式如下:
name_of_parameter: converter
如果参数具有默认值,则将其添加到转换器之后:
name_of_parameter: converter = default_value
Argument Clinic 对“默认值”的支持非常复杂。请参阅以下有关默认值的部分以获取更多信息。
在参数下方添加空白行。
什么是“转换器”?它既构建了 C 中使用的变量的类型,又构建了在运行时将 Python 值转换为 C 值的方法。现在,您将使用所谓的“旧式转换器” —一种便利的语法,旨在使将旧代码移植到 Argument Clinic 变得更容易。
对于每个参数,请从PyArg_Parse()
format 参数复制该参数的“格式单位”,并指定* that *作为其转换器,并用引号引起来。 (“格式单位”是format
参数的一到三个字符子字符串的正式名称,该子字符串告诉参数解析函数变量的类型以及如何对其进行转换.有关格式单位的更多信息,请参见解析参数和构建值。 )
对于z#
之类的多字符格式单位,请使用整个 2 或 3 字符串。
Sample:
/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
- 如果函数的格式字符串中包含
|
,表示某些参数具有默认值,则可以忽略它。参数诊所根据其是否具有默认值来推断哪些参数是可选的。
如果您的函数在格式字符串中具有$
,这意味着它接受仅关键字参数,则在第一个仅关键字参数之前,在一行上自行指定*
,其缩进与参数行相同。
(_pickle.Pickler.dump
都没有,因此我们的示例保持不变.)
- 如果现有的 C 函数调用PyArg_ParseTuple()(而不是PyArg_ParseTupleAndKeywords()),则其所有参数仅是位置参数。
要将所有参数标记为 Argument Clinic 中的仅位置参数,请在最后一个参数之后,在其自身的一行上添加一个/
,其缩进与参数行相同。
目前,这是全有还是全无。所有参数都是仅位置参数,或者都不是。 (将来,Argument Clinic 可能会放宽此限制.)
Sample:
/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
- 为每个参数编写每个参数的文档字符串会很有帮助。但是每个参数的文档字符串是可选的。您可以根据需要跳过此步骤。
这是添加每个参数文档字符串的方法。每个参数文档字符串的第一行必须比参数定义缩进更多。第一行的左边距为整个每个参数的文档字符串构建了左边距;您写的所有文本都将超出此数量。您可以根据需要在多行中写任意数量的文本。
Sample:
/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
The object to be pickled.
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
- 保存并关闭文件,然后在其上运行
Tools/clinic/clinic.py
。一切顺利,幸运的是,您的代码块现在已输出,并且已生成.c.h
文件!在文本编辑器中重新打开文件以查看:
/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
The object to be pickled.
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
static PyObject *
_pickle_Pickler_dump(PicklerObject *self, PyObject *obj)
/*[clinic end generated code: output=87ecad1261e02ac7 input=552eb1c0f52260d9]*/
显然,如果 Argument Clinic 没有产生任何输出,那是因为它在您的 Importing 中发现了错误。continue 修正错误并重试,直到 Argument Clinic 处理您的文件而没有投诉。
为了便于阅读,大多数粘合代码已生成到.c.h
文件。您需要将其包括在原始.c
文件中,通常在诊所模块块之后:
#include "clinic/_pickle.c.h"
- 仔细检查生成的参数解析代码 Argument Clinic 看起来与现有代码基本相同。
首先,确保两个地方使用相同的参数解析函数。现有代码必须调用PyArg_ParseTuple()或PyArg_ParseTupleAndKeywords();确保由 Argument Clinic 生成的代码调用* exact *相同的函数。
其次,传递给PyArg_ParseTuple()或PyArg_ParseTupleAndKeywords()的格式字符串应*与现有函数中的手写格式完全相同,直到冒号或分号为止。
(Argument Clinic 总是在生成其格式字符串时带有:
,后跟函数名称.如果现有代码的格式字符串以;
结尾,以提供使用帮助,则此更改无害-不必担心.)
第三,对于格式单位需要两个参数的参数(例如长度变量,编码字符串或指向转换函数的指针),请确保第二个参数在两次调用之间“完全相同”。
第四,在该块的输出部分中,您将找到一个预处理器宏,用于为此内置函数定义适当的静态PyMethodDef结构:
#define __PICKLE_PICKLER_DUMP_METHODDEF \
{"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__},
该静态结构应该与该内置的现有静态PyMethodDef结构*完全相同。
如果这些项目在方面有任何不同,请调整您的 Argument Clinic 函数规范并重新运行Tools/clinic/clinic.py
,直到*相同。
- 请注意,其输出的最后一行是“ impl”函数的语句。这是内建实现的地方。删除您要修改的函数的现有原型,但保留左花括号。现在删除其参数解析代码和将参数转储到的所有变量的语句。注意,Python 参数现在是该 impl 函数的参数;如果实现对这些变量使用了不同的名称,请对其进行修复。
让我们重申一下,只是因为它有点怪异。您的代码现在应如下所示:
static return_type
your_function_impl(...)
/*[clinic end generated code: checksum=...]*/
{
...
Argument Clinic 在其上方生成了校验和行和函数原型。您应该为函数及其内部的实现写上开(和闭)花括号。
Sample:
/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/
/*[clinic end generated code: checksum=da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
The object to be pickled.
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
PyDoc_STRVAR(__pickle_Pickler_dump__doc__,
"Write a pickled representation of obj to the open file.\n"
"\n"
...
static PyObject *
_pickle_Pickler_dump_impl(PicklerObject *self, PyObject *obj)
/*[clinic end generated code: checksum=3bd30745bf206a48f8b576a1da3d90f55a0a4187]*/
{
/* Check whether the Pickler was initialized correctly (issue3664).
Developers often forget to call __init__() in their subclasses, which
would trigger a segfault without this check. */
if (self->write == NULL) {
PyErr_Format(PicklingError,
"Pickler.__init__() was not called by %s.__init__()",
Py_TYPE(self)->tp_name);
return NULL;
}
if (_Pickler_ClearBuffer(self) < 0)
return NULL;
...
- 还记得此Function的PyMethodDef结构宏吗?找到该函数的现有PyMethodDef结构,并用对宏的引用替换它。 (如果内建函数在模块作用域内,则可能会在文件末尾很近;如果内建函数是类方法,则可能会在文件的末尾,但相对靠近实现.)
请注意,宏的主体包含尾随逗号。因此,当您用宏替换现有的静态PyMethodDef结构时,请不要在末尾添加逗号。
Sample:
static struct PyMethodDef Pickler_methods[] = {
__PICKLE_PICKLER_DUMP_METHODDEF
__PICKLE_PICKLER_CLEAR_MEMO_METHODDEF
{NULL, NULL} /* sentinel */
};
- 编译,然后运行回归测试套件的相关部分。此更改不应引入任何新的编译时警告或错误,并且 Python 行为不应在外部可见。
好吧,除了一个区别:在函数上运行inspect.signature()
现在应该提供有效的签名!
恭喜,您已将您的第一个Function移植到 Argument Clinic!
Advanced Topics
既然您已有使用 Argument Clinic 的经验,那么现在该讨论一些高级主题了。
符号默认值
您为参数提供的默认值不能是任意表达式。当前明确支持以下内容:
数值常数(整数和浮点数)
String constants
True
,False
和None
简单的符号常量,例如
sys.maxsize
,必须以模块的名称开头
如果您感到好奇,可以在from_builtin()
的Lib/inspect.py
中实现。
(将来,可能需要更详细地说明,以允许像CONSTANT - 1
这样的完整表达式.)
重命名 Argument Clinic 生成的 C 函数和变量
Argument Clinic 会自动命名它为您生成的Function。如果生成的名称与现有 C 函数的名称冲突,有时这可能会导致问题。有一个简单的解决方案:覆盖用于 C 函数的名称。只需在函数语句行中添加关键字"as"
,然后添加要使用的函数名即可。 Argument Clinic 将使用该函数名称作为基本(生成的)函数,然后在末尾添加"_impl"
并将其用作 impl 函数的名称。
例如,如果我们想重命名为pickle.Pickler.dump
生成的 C 函数名称,它将看起来像这样:
/*[clinic input]
pickle.Pickler.dump as pickler_dumper
...
基本函数现在将命名为pickler_dumper()
,而 impl 函数现在将命名为pickler_dumper_impl()
。
同样,在给参数指定特定的 Python 名称时可能会遇到问题,但是该名称在 C 语言中可能不方便。Argument Clinic 允许您使用相同的"as"
语法在 Python 和 C 语言中为参数赋予不同的名称:
/*[clinic input]
pickle.Pickler.dump
obj: object
file as file_obj: object
protocol: object = NULL
*
fix_imports: bool = True
在这里,在 Python 中使用的名称(在签名和keywords
数组中)将是file
,但 C 变量将被命名为file_obj
。
您也可以使用它来重命名self
参数!
使用 PyArg_UnpackTuple 转换函数
要转换使用PyArg_UnpackTuple()解析其参数的函数,只需写出所有参数,并将每个参数指定为object
即可。您可以指定type
参数以强制转换类型。所有参数都应标记为“仅位置”(在最后一个参数之后单独在一行上添加/
)。
当前,生成的代码将使用PyArg_ParseTuple(),但不久后将更改。
Optional Groups
一些旧版函数有一种棘手的方法来解析其参数:它们计算位置参数的数量,然后使用switch
语句来调用多个不同的PyArg_ParseTuple()调用中的一个,具体取决于有多少个位置参数。 (这些函数不能接受仅关键字的参数.)此方法用于模拟创建PyArg_ParseTupleAndKeywords()之前的可选参数。
尽管使用这种方法的函数通常可以转换为使用PyArg_ParseTupleAndKeywords(),可选参数和默认值,但这并不总是可能的。其中一些旧版Function的行为PyArg_ParseTupleAndKeywords()不直接支持。最明显的例子是内置函数range()
,该函数在其必选参数的* left *侧具有一个可选参数!另一个示例是curses.window.addch()
,它具有两个必须始终一起指定的参数。 (这些参数分别称为x
和y
;如果您调用x
传递的函数,则还必须传递y
;如果不传递x
,则也不能传递y
.)
无论如何,Argument Clinic 的目标是在不更改其语义的情况下支持所有现有 CPython 内置插件的参数解析。因此,Argument Clinic 使用称为* optional groups *的方法支持这种替代的解析方法。可选组是必须一起传递的参数组。它们可以在所需参数的左侧或右侧。它们只能与仅位置参数一起使用。
Note
可选组仅用于转换将多个调用PyArg_ParseTuple()的函数时使用!使用其他其他方法解析参数的函数绝对不要使用可选组转换为 Argument Clinic。目前,使用可选组的函数在 Python 中不能具有准确的签名,因为 Python 只是不理解这个概念。请尽量避免使用可选组。
要指定可选组,请在要分组在一起的参数之前单独在一行上添加[
,然后在这些参数之后单独在一行上添加]
。例如,以下是curses.window.addch
如何使用可选组将前两个参数和最后一个参数设为可选的方式:
/*[clinic input]
curses.window.addch
[
x: int
X-coordinate.
y: int
Y-coordinate.
]
ch: object
Character to add.
[
attr: long
Attributes for the character.
]
/
...
Notes:
对于每个可选组,将向表示该组的 impl 函数传递一个附加参数。参数将是一个名为
group_{direction}_{number}
的整数,其中{direction}
是right
或left
,具体取决于组是在所需参数之前还是之后,而{number}
是单调递增的数字(从 1 开始),指示组与参数之间的距离。必需的参数。调用 impl 时,如果未使用此组,则此参数将设置为零;如果使用此组,则将其设置为非零。 (pass使用还是未使用,我的意思是参数是否在此调用中接收了参数.)如果没有必需的参数,则可选组的行为就像它们在必需参数的右侧一样。
在模棱两可的情况下,参数解析代码偏向于左侧的参数(在所需参数之前)。
可选组只能包含仅位置参数。
可选组仅用于旧代码。请不要对新代码使用可选组。
使用 true 的 Argument Clinic 转换器,而不是“旧版转换器”
为了节省时间并最大程度地减少学习到 Argument Clinic 的第一个端口,上面的演练告诉您使用“旧版转换器”。 “旧式转换器”是一种便利,它经过明确设计,可以更轻松地将现有代码移植到 Argument Clinic。需要明确的是,在为 Python 3.4 移植代码时,可以使用它们。
但是,从长远来看,我们可能希望我们所有的块都将 Argument Clinic 的实际语法用于转换器。为什么?几个原因:
正确的转换器在意图上更容易阅读和清楚。
有些格式单位不支持作为“旧版转换器”,因为它们需要参数,而传统转换器语法不支持指定参数。
将来我们可能会有一个新的参数解析库,它不限于PyArg_ParseTuple()支持的内容;使用传统转换器的参数将无法获得这种灵 Active。
因此,如果您不介意花费额外的精力,请使用常规转换器而不是传统转换器。
简而言之,Argument Clinic(非旧版)转换器的语法看起来像 Python 函数调用。但是,如果该函数没有显式参数(所有函数均采用其默认值),则可以Ellipsis括号。因此bool
和bool()
是完全相同的转换器。
Argument Clinic 转换器的所有参数都是仅关键字的。所有 Argument Clinic 转换器都接受以下参数:
Note
另外,某些转换器接受其他参数。以下是这些参数及其含义的列表:
Note
accept
一组 Python 类型(可能还有伪类型);这将允许的 Python 参数限制为这些类型的值。 (这不是通用工具;通常,它仅支持特定类型的列表,如旧版转换器表中所示.)
要接受None
,请将NoneType
添加到该集合中。
bitwise
仅支持无符号整数。该 Python 参数的本机整数值将被写入参数,而无需进行任何范围检查,即使对于负值也是如此。
converter
仅由
object
转换器支持。指定用于将此对象转换为纯类型的C“转换器Function”的名称。encoding
仅支持字符串。指定将此字符串从 Python str(Unicode)值转换为 C
char *
值时要使用的编码。subclass_of
仅支持
object
转换器。要求 Python 值是 Python 类型的子类,以 C 表示。type
仅支持
object
和self
转换器。指定将用于语句变量的 C 类型。默认值为"PyObject *"
。zeroes
仅支持字符串。如果为 true,则在值内允许嵌入 NUL 字节(
'\\0'
)。字符串的长度将在字符串参数之后作为名为<parameter_name>_length
的参数传递给 impl 函数。
请注意,并非所有可能的参数组合都适用。通常,这些参数由具有特定行为的特定PyArg_ParseTuple
* format units *实现。例如,当前您不能在不指定bitwise=True
的情况下调用unsigned_short
。尽管认为这行得通是完全合理的,但是这些语义并不 Map 到任何现有的格式单元。因此,Argument Clinic 不支持它。 (或者至少还没有.)
下表显示了旧式转换器到实际 Argument Clinic 转换器的 Map。左侧是旧版转换器,右侧是您要替换为的文本。
'B' |
unsigned_char(bitwise=True) |
'b' |
unsigned_char |
'c' |
char |
'C' |
int(accept={str}) |
'd' |
double |
'D' |
Py_complex |
'es' |
str(encoding='name_of_encoding') |
'es#' |
str(encoding='name_of_encoding', zeroes=True) |
'et' |
str(encoding='name_of_encoding', accept={bytes, bytearray, str}) |
'et#' |
str(encoding='name_of_encoding', accept={bytes, bytearray, str}, zeroes=True) |
'f' |
float |
'h' |
short |
'H' |
unsigned_short(bitwise=True) |
'i' |
int |
'I' |
unsigned_int(bitwise=True) |
'k' |
unsigned_long(bitwise=True) |
'K' |
unsigned_long_long(bitwise=True) |
'l' |
long |
'L' |
long long |
'n' |
Py_ssize_t |
'O' |
object |
'O!' |
object(subclass_of='&PySomething_Type') |
'O&' |
object(converter='name_of_c_function') |
'p' |
bool |
'S' |
PyBytesObject |
's' |
str |
's#' |
str(zeroes=True) |
's*' |
Py_buffer(accept={buffer, str}) |
'U' |
unicode |
'u' |
Py_UNICODE |
'u#' |
Py_UNICODE(zeroes=True) |
'w*' |
Py_buffer(accept={rwbuffer}) |
'Y' |
PyByteArrayObject |
'y' |
str(accept={bytes}) |
'y#' |
str(accept={robuffer}, zeroes=True) |
'y*' |
Py_buffer |
'Z' |
Py_UNICODE(accept={str, NoneType}) |
'Z#' |
Py_UNICODE(accept={str, NoneType}, zeroes=True) |
'z' |
str(accept={str, NoneType}) |
'z#' |
str(accept={str, NoneType}, zeroes=True) |
'z*' |
Py_buffer(accept={buffer, str, NoneType}) |
举例来说,这是使用适当的转换器的示例pickle.Pickler.dump
:
/*[clinic input]
pickle.Pickler.dump
obj: object
The object to be pickled.
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
true 的转换器的优点之一是它们比传统转换器更灵活。例如,可以不使用bitwise=True
来指定unsigned_int
转换器(以及所有unsigned_
转换器)。它们的默认行为会对值进行范围检查,并且它们将不接受负数。您只是用旧版转换器无法做到这一点!
Argument Clinic 将向您显示其可用的所有转换器。对于每个转换器,它将显示您接受的所有参数以及每个参数的默认值。只需运行Tools/clinic/clinic.py --converters
即可查看完整列表。
Py_buffer
使用Py_buffer
转换器(或's*'
,'w*'
,'*y'
或'z*'
旧式转换器)时,您必须不要在提供的缓冲区上调用PyBuffer_Release()。 Argument Clinic 会为您生成代码(在解析函数中)。
Advanced converters
还记得那些由于高级而第一次跳过的格式单位吗?这也是处理这些问题的方法。
诀窍是,所有这些格式单位都带有参数-转换函数或类型,或指定编码的字符串。 (但是“旧版转换器”不支持参数.这就是我们在第一个函数中跳过它们的原因.)现在,您为 format 单位指定的参数是转换器的参数。此参数是converter
(对于O&
),subclass_of
(对于O!
)或encoding
(对于以e
开头的所有格式单位)。
使用subclass_of
时,您可能还想为object()
使用另一个自定义参数:type
,该参数可让您设置实际用于该参数的类型。例如,如果要确保对象是PyUnicode_Type
的子类,则可能要使用转换器object(type='PyUnicodeObject *', subclass_of='&PyUnicode_Type')
。
使用 Argument Clinic 的一个可能的问题:对于以e
开头的格式单位,它失去了一些可能的灵 Active。手动编写PyArg_Parse
调用时,理论上可以在运行时决定将哪种编码字符串传递给PyArg_ParseTuple()。但是现在,此字符串必须在 Argument-Clinic-preprocessing-time 时进行硬编码。此限制是有意的;它使支持此格式单元变得更加容易,并且可能会在将来进行优化。这种限制似乎并不合理。 CPython 本身总是为格式单位以e
开头的参数传递静态硬编码的编码字符串。
参数默认值
参数的默认值可以是任意多个值。简单来说,它们可以是字符串,整数或浮点 Literals:
foo: str = "abc"
bar: int = 123
bat: float = 45.6
他们还可以使用 Python 的任何内置常量:
yep: bool = True
nope: bool = False
nada: object = None
还特别支持默认值NULL
以及以下部分中记录的简单表达式。
NULL 的默认值
对于字符串和对象参数,您可以将它们设置为None
表示没有默认值。但是,这意味着 C 变量将被初始化为Py_None
。为了方便起见,正因为如此,有一个名为NULL
的特殊值:从 Python 的角度来看,它的行为类似于None
的默认值,但 C 变量是使用NULL
初始化的。
指定为默认值的表达式
参数的默认值可以不仅仅是 Literals 值。它可以是整个表达式,使用 math 运算符并在对象上查找属性。但是,由于某些非显而易见的语义,这种支持并不十分简单。
考虑以下示例:
foo: Py_ssize_t = sys.maxsize - 1
sys.maxsize
在不同平台上可以具有不同的值。因此,Argument Clinic 不能简单地在本地评估该表达式并将其用 C 硬编码。因此,它存储默认值,以便在用户要求函数签名时在运行时对其进行评估。
计算表达式时可以使用什么名称空间?在内置模块来自的上下文中对其进行评估。因此,如果您的模块具有名为“ max_widgets
”的属性,则可以简单地使用它:
foo: Py_ssize_t = max_widgets
如果在当前模块中找不到该符号,则它将故障转移到sys.modules
中。例如,这就是它可以找到sys.maxsize
的方式。 (由于您事先不知道用户会将哪些模块加载到其解释器中,因此最好将自己限制为使用 Python 本身预加载的模块.)
仅在运行时评估默认值意味着 Argument Clinic 无法计算正确的等效 C 默认值。因此,您需要明确地告诉它。使用表达式时,还必须在 C 中使用转换器的c_default
参数指定等效表达式:
foo: Py_ssize_t(c_default="PY_SSIZE_T_MAX - 1") = sys.maxsize - 1
另一个复杂之处是:Argument Clinic 无法事先知道您提供的表达式是否有效。它会对其进行解析以确保它看起来合法,但实际上无法知道。使用表达式指定保证在运行时有效的值时必须非常小心!
最后,由于表达式必须可表示为静态 C 值,因此对合法表达式有很多限制。以下是您不允许使用的 Python Function列表:
Function calls.
内联 if 语句(
3 if foo else 5
)。自动序列拆包(
*[1, 2, 3]
)。列出/设置/ dict 理解和生成器表达式。
Tuple/list/set/dict literals.
使用返回转换器
默认情况下,Argument Clinic 为您生成的 impl 函数返回PyObject *
。但是您的 C 函数通常会计算一些 C 类型,然后在最后一刻将其转换为PyObject *
。 Argument Clinic 负责将您的 Importing 从 Python 类型转换为本地 C 类型-为什么不将返回值也从本地 C 类型转换为 Python 类型?
这就是“返回转换器”的作用。它更改您的 impl 函数以返回某些 C 类型,然后将代码添加到生成的(非 impl)函数中,以处理将该值转换为适当的PyObject *
。
返回转换器的语法类似于参数转换器的语法。您可以指定返回转换器,就像它是函数本身的返回 Comments 一样。返回转换器的行为与参数转换器的行为大致相同。它们带有参数,参数都是纯关键字,如果不更改任何默认参数,则可以Ellipsis括号。
(如果您同时使用"as"
和返回转换器,则"as"
应该位于返回转换器之前.)
使用返回转换器时还有一个复杂的问题:如何指示已发生错误?通常,一个函数返回一个有效的(非NULL
)指针以表示成功,而返回NULL
则表示失败。但是,如果使用整数返回转换器,则所有整数均有效。 Argument Clinic 如何检测错误?解决方案:每个返回转换器隐式地寻找一个指示错误的特殊值。如果返回该值,并且设置了错误(PyErr_Occurred()
返回真实值),则生成的代码将传播该错误。否则,它将像平常一样对您返回的值进行编码。
当前,Argument Clinic 仅支持一些返回转换器:
bool
int
unsigned int
long
unsigned int
size_t
Py_ssize_t
float
double
DecodeFSDefault
这些都不带参数。对于前三个,返回-1 表示错误。对于DecodeFSDefault
,返回类型为const char *
;返回NULL
指针以指示错误。
(还有一个实验性的NoneType
转换器,让您在成功时返回Py_None
或在失败时返回NULL
,而不必增加Py_None
上的引用计数.我不确定它是否增加了足够的清晰度值得使用.)
要查看 Argument Clinic 支持的所有返回转换器及其参数(如果有),只需对完整列表运行Tools/clinic/clinic.py --converters
即可。
克隆现有Function
如果您有许多看起来相似的Function,则可以使用 Clinic 的“克隆”Function。克隆现有函数时,可以重用:
其参数,包括
their names,
他们的转换器,带有所有参数,
它们的默认值,
他们的每个参数文档字符串
他们的种类(无论是仅排名,排名还是关键字,还是仅关键字),以及
它的返回转换器。
没有从原始函数复制的唯一内容是它的文档字符串;语法允许您指定新的文档字符串。
这是克隆函数的语法:
/*[clinic input]
module.class.new_function [as c_basename] = module.class.existing_function
Docstring for new_function goes here.
[clinic start generated code]*/
(这些函数可以在不同的模块或类中.我在示例中写了module.class
只是为了说明您必须使用两个函数的完整路径.)
抱歉,没有语法可以部分克隆一个函数,或者先克隆一个函数然后对其进行修改。克隆是一个全有或全无的主张。
另外,要克隆的Function必须已经在当前文件中预先定义。
调用 Python 代码
其余的高级主题要求您编写 Python 代码,该代码位于 C 文件中,并修改 Argument Clinic 的运行时状态。这很简单:您只需定义一个 Python 块。
Python 块使用与 Argument Clinic Function块不同的定界线。看起来像这样:
/*[python input]
# python code goes here
[python start generated code]*/
Python 块中的所有代码都在解析时执行。块内写入 stdout 的所有文本都将重定向到该块之后的“输出”中。
例如,下面是一个 Python 块,该块向 C 代码添加了一个静态整数变量:
/*[python input]
print('static int __ignored_unused_variable__ = 0;')
[python start generated code]*/
static int __ignored_unused_variable__ = 0;
/*[python checksum:...]*/
使用“自我转换器”
Argument Clinic 使用默认转换器自动为您添加“自我”参数。它将自动将此参数的type
设置为在语句类型时指定的“实例的指针”。但是,您可以覆盖 Argument Clinic 的转换器并自己指定一个。只需将自己的self
参数添加为块中的第一个参数,并确保其转换器是self_converter
的实例或其子类。
重点是什么?这使您可以覆盖self
的类型,或为其指定其他默认名称。
如何指定要投射self
的自定义类型?如果self
只有一个或两个具有相同类型的函数,则可以直接使用 Argument Clinic 的现有self
转换器,并传入要用作type
参数的类型:
/*[clinic input]
_pickle.Pickler.dump
self: self(type="PicklerObject *")
obj: object
/
Write a pickled representation of the given object to the open file.
[clinic start generated code]*/
另一方面,如果您有很多函数将对self
使用相同的类型,则最好创建自己的转换器,将self_converter
子类化,但覆盖type
成员:
/*[python input]
class PicklerObject_converter(self_converter):
type = "PicklerObject *"
[python start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
self: PicklerObject
obj: object
/
Write a pickled representation of the given object to the open file.
[clinic start generated code]*/
编写自定义转换器
正如我们在上一节中所暗示的那样……您可以编写自己的转换器!转换器只是从CConverter
继承的 Python 类。自定义转换器的主要目的是,如果您有一个使用O&
格式单位的参数-解析此参数意味着调用PyArg_ParseTuple()“转换器Function”。
您的转换器类应命名为*something*_converter
。如果名称遵循该约定,那么您的转换器类将自动在 Argument Clinic 中注册;它的名称将是带有_converter
后缀的类名称。 (这是pass元类完成的.)
您不应该继承CConverter.__init__
。相反,您应该编写一个converter_init()
函数。 converter_init()
始终接受self
参数;之后,所有其他参数必须仅是关键字。在 Argument Clinic 中传递给转换器的所有参数都将传递给converter_init()
。
您可能希望在子类中指定CConverter
的其他成员。这是当前列表:
type
- 用于此变量的 C 类型。
type
应该是指定类型的 Python 字符串,例如int
。如果这是指针类型,则类型字符串应以' *'
结尾。
- 用于此变量的 C 类型。
default
- 此参数的 Python 默认值,作为 Python 值。或魔术值
unspecified
(如果没有默认值)。
- 此参数的 Python 默认值,作为 Python 值。或魔术值
py_default
default
,应以字符串形式显示在 Python 代码中。或None
(如果没有默认值)。
c_default
default
(应在 C 代码中显示为字符串)。或None
(如果没有默认值)。
c_ignored_default
- 没有默认值时用于初始化 C 变量的默认值,但未指定默认值可能会导致“未初始化的变量”警告。使用选项组时很容易发生这种情况-尽管正确编写的代码永远不会实际使用该值,但变量确实会传递给 impl,并且 C 编译器会抱怨未初始化值的“使用”。此值应始终为非空字符串。
converter
- C 转换器函数的名称,以字符串形式。
impl_by_reference
- 布尔值。如果为 true,则将变量传递给 impl 函数时,Argument Clinic 将在变量名称之前添加
&
。
- 布尔值。如果为 true,则将变量传递给 impl 函数时,Argument Clinic 将在变量名称之前添加
parse_by_reference
- 布尔值。如果为 true,则将变量传递到PyArg_ParseTuple()时,Argument Clinic 将在变量名称之前添加
&
。
- 布尔值。如果为 true,则将变量传递到PyArg_ParseTuple()时,Argument Clinic 将在变量名称之前添加
这是来自Modules/zlibmodule.c
的最简单的自定义转换器示例:
/*[python input]
class ssize_t_converter(CConverter):
type = 'Py_ssize_t'
converter = 'ssize_t_converter'
[python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=35521e4e733823c7]*/
此块将一个名为ssize_t
的转换器添加到 Argument Clinic。语句为ssize_t
的参数将语句为Py_ssize_t
类型,并由'O&'
格式单元进行解析,该格式单元将调用ssize_t_converter
转换器函数。 ssize_t
变量自动支持默认值。
更复杂的自定义转换器可以插入自定义 C 代码来处理初始化和清除。您可以在 CPython 源代码树中看到更多自定义转换器的示例。 grep 字符串CConverter
的 C 文件。
编写自定义返回转换器
编写自定义返回转换器与编写自定义转换器非常相似。除了稍微简单一点,因为返回转换器本身也简单得多。
返回转换器必须是CReturnConverter
的子类。定制返回转换器的例子还没有,因为它们还没有被广泛使用。如果您想编写自己的返回转换器,请阅读Tools/clinic/clinic.py
,特别是CReturnConverter
及其所有子类的实现。
METH_O 和 METH_NOARGS
要使用METH_O
转换函数,请确保该函数的单个参数使用object
转换器,并将这些参数标记为仅位置:
/*[clinic input]
meth_o_sample
argument: object
/
[clinic start generated code]*/
要使用METH_NOARGS
转换函数,只需不指定任何参数即可。
您仍然可以使用自转换器,返回转换器,并为METH_O
指定对象转换器的type
参数。
tp_new 和 tp_init 函数
您可以转换tp_new
和tp_init
函数。只需将它们命名为__new__
或__init__
即可。笔记:
为
__new__
生成的函数名称不像默认情况下那样以__new__
结尾。它只是类的名称,被转换为有效的 C 标识符。这些Function不会产生
PyMethodDef
#define
。__init__
个函数返回int
,而不是PyObject *
。使用文档字符串作为类文档字符串。
尽管
__new__
和__init__
函数必须始终接受args
和kwargs
对象,但是在转换时,可以为所需的这些函数指定任何签名。 (如果您的函数不支持关键字,则生成的解析函数将在接收到任何异常时抛出异常.)
更改和重定向诊所的输出
将 Clinic 的输出插入常规的手工编辑的 C 代码可能会很不方便。幸运的是,Clinic 是可配置的:您可以缓冲其输出以供以后(或更早!)打印,或将其输出写入单独的文件。您还可以在 Clinic 生成的输出的每一行中添加前缀或后缀。
虽然以这种方式更改 Clinic 的输出可能会提高可读性,但可能会导致 Clinic 代码在定义之前使用类型,或者您的代码try在 Clinic 定义之前使用生成的代码。pass重新排列文件中的语句或将 Clinic 生成的代码移到何处,可以轻松解决这些问题。 (这就是为什么 Clinic 的默认行为是将所有内容输出到当前块中的原因;尽管许多人认为这会影响可读性,但它永远不需要重新排列代码来解决使用前定义的问题.)
让我们从定义一些术语开始:
field
- 在这种情况下,字段是 Clinic 输出的子部分。例如,
PyMethodDef
结构的#define
是一个名为methoddef_define
的字段。诊所有七个不同的字段,可以根据Function定义输出:
- 在这种情况下,字段是 Clinic 输出的子部分。例如,
docstring_prototype
docstring_definition
methoddef_define
impl_prototype
parser_prototype
parser_definition
impl_definition
所有名称的格式均为"<a>_<b>"
,其中"<a>"
是表示的语义对象(解析函数,impl 函数,docstring 或 methoddef 结构),而"<b>"
表示字段是哪种语句。以"_prototype"
结尾的字段名称表示该事物的前向语句,而没有该事物的实际主体/数据。以"_definition"
结尾的字段名称代表事物的实际定义以及事物的主体/数据。 ("methoddef"
是特殊的,它是唯一以"_define"
结尾的,表示它是预处理器#define.)
destination
- 目的地是诊所可以将输出写入的地方。有五个内置目的地:
block
默认目标:打印在当前诊所模块的输出部分。
buffer
- 文本缓冲区,您可以在其中保存文本以备后用。此处发送的文本会附加到任何现有文本的末尾。诊所完成文件处理后,在缓冲区中保留任何文本是错误的。
file
- 诊所将自动创建一个单独的“诊所文件”。为该文件选择的文件名是
{basename}.clinic{extension}
,其中basename
和extension
被分配了os.path.splitext()
在当前文件上运行的输出。 (示例:_pickle.c
的file
目标将被写入_pickle.clinic.c
.)
- 诊所将自动创建一个单独的“诊所文件”。为该文件选择的文件名是
重要提示:使用 file
目的地时,您 必须签入 生成的文件!
two-pass
- 像
buffer
这样的缓冲区。但是,两次pass的缓冲区只能转储一次,并且即使在转储点之后*,即使在诊所模块中,它也会打印出在所有处理期间发送给它的所有文本。
- 像
suppress
- 文本被隐藏-被丢弃。
诊所定义了五个新指令,可让您重新配置其输出。
第一个新指令是dump
:
dump <destination>
这会将指定目标的当前内容转储到当前块的输出中,并将其清空。这仅适用于buffer
和two-pass
目的地。
第二个新指令是output
。 output
的最基本形式如下:
output <field> <destination>
这告诉诊所将* field 输出到 destination 。 output
还支持称为everything
的特殊元目标,它告诉 Clinic 将 all 字段输出到该 destination *。
output
具有许多其他Function:
output push
output pop
output preset <preset>
output push
和output pop
允许您在内部配置堆栈中推送和弹出配置,以便您可以临时修改输出配置,然后轻松还原以前的配置。只需在更改前按一下即可保存当前配置,然后在要还原以前的配置时弹出。
output preset
将 Clinic 的输出设置为几种内置预设配置之一,如下所示:
Note
block
诊所的原始启动配置。在 Importing 块之后立即写入所有内容。
禁止parser_prototype
和docstring_prototype
,将其他所有内容都写入block
。
file
旨在将所有内容写入它可以的“诊所文件”中。然后,您在文件顶部附近
#include
此文件。您可能需要重新排列文件以使此工作正常进行,尽管通常这仅意味着为各种typedef
和PyTypeObject
定义创建前向语句。
抑制parser_prototype
和docstring_prototype
,将impl_definition
写入block
,然后将其他所有内容写入file
。
默认文件名是"{dirname}/clinic/{basename}.h"
。
buffer
保存 Clinic 的大部分输出,以将其写入文件末尾。对于实现模块或内置类型的 Python 文件,建议将缓冲区转储到模块或内置类型的静态结构的上方;这些通常都快结束了。如果您的文件在文件中间定义了静态
PyMethodDef
数组,则使用buffer
可能需要比file
进行更多的编辑。
抑制parser_prototype
,impl_prototype
和docstring_prototype
,将impl_definition
写入block
,然后将其他所有内容写入file
。
two-pass
与
buffer
预设类似,但是将前向语句写入two-pass
缓冲区,并将定义写入buffer
。这与buffer
预设相似,但是可能需要的编辑次数少于buffer
。将two-pass
缓冲区转储到文件顶部附近,并将buffer
转储到末尾附近,就像使用buffer
预设时一样。
禁止impl_prototype
,将impl_definition
写入block
,将docstring_prototype
,methoddef_define
和parser_prototype
写入two-pass
,将其他所有内容写入buffer
。
partial-buffer
类似于
buffer
预设,但将更多内容写入block
,仅将生成的代码的很大一部分写入buffer
。这样就完全避免了buffer
的使用前定义问题,所付出的代价是在块的输出中包含更多的内容。就像在使用buffer
预设时一样,将buffer
转储到末尾。
禁止impl_prototype
,将docstring_definition
和parser_definition
写入buffer
,将其他所有内容写入block
。
第三个新指令是destination
:
destination <name> <command> [...]
这将在名为name
的目的地上执行操作。
定义了两个子命令:new
和clear
。
new
子命令的工作方式如下:
destination <name> new <type>
这将创建一个名称为<name>
并键入<type>
的新目的地。
有五种目的地类型:
Note
suppress
扔掉文本。
block
将文本写入当前块。这是诊所最初所做的。
buffer
一个简单的文本缓冲区,例如上面的“ buffer”内置目标。
file
文本文件。文件目的地带有一个额外的参数,一个用于构建文件名的模板,如下所示:
Note
目的地新 <file_template>
模板可以在内部使用三个字符串,这些字符串将被文件名的位替换:
Note
{path}
文件的完整路径,包括目录和完整文件名。
{dirname}
文件所在目录的名称。
{basename}
只是文件名,不包括目录。
{basename_root}
extensions 被截断的基名(所有内容,但不包括最后一个“.”)。
{basename_extension}
最后 '.'以及之后的一切。如果基本名称不包含句点,则为空字符串。
如果文件名中没有句点,则\ {}和\ {}相同,并且\ {}为空。 “{basename}{extension}”始终与“{filename}”完全相同。
two-pass
两遍缓冲区,如上面的“两遍”内置目标。
clear
子命令的工作方式如下:
destination <name> clear
它将删除目的地中到目前为止所有的累积文本。 (我不知道您需要什么,但是我认为这可能在某人进行实验时会很有用.)
第四个新指令是set
:
set line_prefix "string"
set line_suffix "string"
set
可让您在 Clinic 中设置两个内部变量。 line_prefix
是将在 Clinic 输出的每一行之前添加的字符串; line_suffix
是一个字符串,将附加到 Clinic 输出的每一行。
这两个都支持两种格式的字符串:
Note
{block comment start}
变成字符串
/*
,即 C 文件的开始 Comments 文本序列。{block comment end}
变成字符串
*/
,即 C 文件的结尾 Comments 文本序列。
最后一个新指令是您无需直接使用的指令,称为preserve
:
preserve
这告诉 Clinic,输出的当前内容应保持不变。在将输出转储到file
文件中时,这在诊所内部使用;pass将其包装在 Clinic 块中,Clinic 可以使用其现有的校验和Function来确保在覆盖文件之前不对其进行手动修改。
#ifdef 技巧
如果您要转换的Function并非在所有平台上都可用,则可以使用一种技巧来使生活更轻松。现有代码可能如下所示:
#ifdef HAVE_FUNCTIONNAME
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */
然后在底部的PyMethodDef
结构中,现有代码将具有:
#ifdef HAVE_FUNCTIONNAME
{'functionname', ... },
#endif /* HAVE_FUNCTIONNAME */
在这种情况下,应将 impl 函数的主体包含在#ifdef
内,如下所示:
#ifdef HAVE_FUNCTIONNAME
/*[clinic input]
module.functionname
...
[clinic start generated code]*/
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */
然后,从PyMethodDef
结构中删除这三行,并用生成的宏 Argument Clinic 替换它们:
MODULE_FUNCTIONNAME_METHODDEF
(您可以在生成的代码中找到此宏的真实名称.或者您可以自己计算该宏的名称:这是您在代码块第一行中定义的函数的名称,但是句点更改为下划线,大写并添加了"_METHODDEF"
到最后.)
也许您想知道:如果未定义HAVE_FUNCTIONNAME
怎么办? MODULE_FUNCTIONNAME_METHODDEF
宏也不会定义!
这是 Argument Clinic 变得非常聪明的地方。实际上,它检测到#ifdef
可能已停用了 Argument Clinic 模块。发生这种情况时,它将生成一些额外的代码,如下所示:
#ifndef MODULE_FUNCTIONNAME_METHODDEF
#define MODULE_FUNCTIONNAME_METHODDEF
#endif /* !defined(MODULE_FUNCTIONNAME_METHODDEF) */
这意味着宏始终有效。如果定义了函数,它将变成正确的结构,包括结尾的逗号。如果该函数未定义,则什么都没有。
但是,这会引起一个棘手的问题:使用“块”输出预设时,自变量诊所应该在哪里将这些额外的代码放在哪里?它不能进入输出块,因为它可以被#ifdef
禁用。 (这就是重点!)
在这种情况下,Argument Clinic 将多余的代码写入“缓冲区”目标。这可能意味着您会收到 Argument Clinic 的投诉:
Warning in file "Modules/posixmodule.c" on line 12357:
Destination buffer 'buffer' not empty at end of file, emptying.
发生这种情况时,只需打开文件,找到 Argument Clinic 添加到文件中的dump buffer
块(它将在最底部),然后将其移到使用该宏的PyMethodDef
结构上方。
在 Python 文件中使用 Argument Clinic
实际上,可以使用 Argument Clinic 预处理 Python 文件。当然,没有必要使用 Argument Clinic 块,因为输出对于 Python 解释器没有任何意义。但是,使用 Argument Clinic 运行 Python 块可以让您将 Python 用作 Python 预处理器!
由于 PythonComments 与 CComments 不同,因此嵌入在 Python 文件中的 Argument Clinic 块看起来略有不同。他们看起来像这样:
#/*[python input]
#print("def foo(): pass")
#[python start generated code]*/
def foo(): pass
#/*[python checksum:...]*/