Skip to main content

参数诊所如何

author:

拉里·海斯廷斯

抽象

Argument Clinic是CPython C文件的预处理器。它的目的是自动化所有的样板参与解析“builtins”的参数解析代码。本文档介绍如何将您的第一个C函数转换为使用Argument Clinic,然后介绍一些有关Argument Clinic使用的高级主题。

当前Argument Clinic被认为是CPython的内部专用。它不支持对CPython外部的文件的使用,并且不保证未来版本的向后兼容性。换句话说:如果您为CPython维护一个外部C扩展,欢迎您在自己的代码中尝试使用Argument Clinic。但是CPython 3.5 could 附带的Argument Clinic版本完全不兼容,并且破坏了所有代码。

参数诊所的目标

Argument Clinic的主要目标是接管CPython中所有参数解析代码的责任。这意味着,当您转换函数以使用Argument Clinic时,该函数不应再执行任何自己的参数解析 - 由Argument Clinic生成的代码应该是一个“黑盒子”,CPython在其中调用top,你的代码在底部被调用,PyObject *args (也许 PyObject *kwargs)神奇地转换成你需要的C变量和类型。

为了使Argument Clinic完成其主要目标,它必须易于使用。目前,使用CPython的参数解析库是一件繁重的事,需要在令人惊讶的地方保留冗余信息。当你使用Argument Clinic时,你不必重复自己。

显然,除非它解决他们的问题 - 没有创造自己的新问题,没有人会想要使用Argument Clinic。因此,Argument Clinic生成正确的代码是至关重要的。如果代码更快,这将是很好,但至少它不应该引入一个主要的速度回归。 (最终的Argument Clinic should 做了一个大的加速可能 - 我们可以重写它的代码生成器来生成定制的参数解析代码,而不是调用通用的CPython参数解析库,这将是最快的参数解析可能!

此外,Argument Clinic必须足够灵活,可以使用任何方法进行参数解析。 Python有一些功能,有一些非常奇怪的解析行为; Argument Clinic的目标是支持所有这些。

最后,Argument Clinic的原始动机是为CPython内置程序提供内省“签名”。以前是,内省查询函数会抛出一个异常,如果你传递一个内置。使用参数诊所,这是一个过去!

当你使用Argument Clinic时,你应该记住一个想法:你给它的信息越多,它能做的就越好。争论诊所现在比较简单。但随着它的发展,它会变得更加复杂,它应该能够做许多有趣的和聪明的事情与你提供的所有信息。

基本概念和用法

参数诊所附带CPython;你会发现它在 Tools/clinic/clinic.py。如果运行该脚本,请指定C文件作为参数:

% python3 Tools/clinic/clinic.py foo.c

Argument Clinic将扫描文件,查找看起来像这样的线:

/*[clinic input]

当它找到一个,它读取一切,直到一个看起来像这样的线:

[clinic start generated code]*/

这两行之间的所有内容都输入到Argument Clinic。所有这些行,包括开始和结束注释行,统称为Argument Clinic“块”。

当Argument Clinic解析其中一个块时,它会生成输出。此输出将重写入紧接在块之后的C文件中,后面是包含校验和的注释。 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将丢弃旧的输出并使用新的校验和行写出新的输出。但是,如果输入没有改变,输出也不会改变。

不应修改Argument Clinic块的输出部分。相反,请更改输入,直到生成所需的输出。 (这是校验和的目的 - 检测是否有人改变了输出,因为这些编辑将在下一次Argument Clinic写出新的输出时丢失。)

为了清楚起见,下面是我们将使用Argument Clinic的术语:

  • 注释的第一行(/*[clinic input])是 启动线

  • 初始注释([clinic start generated code]*/)的最后一行是 结束行

  • 最后一行(/*[clinic end generated code: checksum=...]*/)是 校验和线

  • 在开始行和结束行之间是 input

  • 在结束行和校验和行之间是 output

  • 所有文本,从起始行到校验和行,包括 block。 (尚未被Argument Clinic成功处理但没有输出或校验和行但仍被视为块的块)。

转换您的第一个功能

了解Argument Clinic如何工作的最好方法是将一个函数转换为使用它。在这里,然后,是你需要遵循的转换一个函数使用Argument Clinic的最低限度的步骤。请注意,对于计划签入CPython的代码,您应该使用一些高级概念(如“返回转换器”和“自转换器”),以便进一步完成转换。但我们会保持这个演练的简单,所以你可以学习。

让我们潜入!

  1. 确保您使用的是CPython干线的新更新检查。

  2. 找到一个调用 PyArg_ParseTuple()PyArg_ParseTupleAndKeywords() 的Python内置函数,并且尚未转换为使用Argument Clinic。对我的例子,我使用 _pickle.Pickler.dump()

  3. 如果对 PyArg_Parse 函数的调用使用以下任何格式单位:

    O&
    O!
    es
    es#
    et
    et#
    

    或者如果它有多个调用 PyArg_ParseTuple(),你应该选择一个不同的功能。参数诊所 does 支持所有这些情况。但是这些都是高级主题 - 让我们为你的第一个功能做一些更简单的事情。

    此外,如果函数有多个调用 PyArg_ParseTuple()PyArg_ParseTupleAndKeywords(),它支持同一个参数的不同类型,或者函数使用除了PyArg_Parse函数之外的东西来解析它的参数,它可能不适合转换到Argument Clinic。 Argument Clinic不支持通用函数或多态参数。

  4. 在函数上面添加以下样板,创建我们的块:

    /*[clinic input]
    [clinic start generated code]*/
    
  5. 剪切文档字符串并将其粘贴在 [clinic] 行之间,删除所有垃圾,使其成为正确引用的C字符串。完成后,您应该只有基于左边距的文本,没有超过80个字符的线。 (参数临床将保留docstring内的缩进。)

    如果旧的docstring有第一行看起来像一个函数签名,抛出那行。 (docstring不再需要它 - 当您在将来使用 help() 在内置时,第一行将基于函数的签名自动构建。)

    样品:

    /*[clinic input]
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  6. 如果你的docstring没有“summary”行,Argument Clinic会抱怨。所以让我们确保它有一个。 “summary”行应该是一个由docstring开头的单个80列行组成的段落。

    (我们的示例docstring只包含一个摘要行,因此示例代码不必为此步骤更改。)

  7. 在docstring上方,输入函数的名称,后跟一个空行。这应该是函数的Python名称,并且应该是函数的完整的虚线路径 - 它应该以模块的名称开始,包括任何子模块,如果函数是类上的方法,它应该包括类名太。

    样品:

    /*[clinic input]
    _pickle.Pickler.dump
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  8. 如果这是第一次在此C文件中使用Argument Clinic的模块或类,则必须声明模块和/或类。正确的参数临床卫生更喜欢在靠近C文件顶部的单独块中声明这些,包括文件和静态在顶部的相同方式。 (在我们的示例代码中,我们将只显示两个相邻的块)。

    类和模块的名称应该与Python看到的名称相同。检查 PyModuleDefPyTypeObject 中定义的名称是否合适。

    当你声明一个类时,你还必须在C语言中指定它的类型的两个方面:你用于指向这个类的实例的类型声明,以及一个指向这个类的 PyTypeObject 的指针。

    样品:

    /*[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]*/
    
  9. 声明函数的每个参数。每个参数应该有自己的行。所有参数行都应该缩进函数名和docstring。

    这些参数行的一般形式如下:

    name_of_parameter: converter
    

    如果参数具有默认值,则在转换器后添加:

    name_of_parameter: converter = default_value
    

    参数诊所对“默认值”的支持非常复杂;请参阅 以下关于默认值的部分 了解更多信息。

    在参数下方添加空行。

    什么是“转换器”?它建立在C中使用的变量的类型,以及在运行时将Python值转换为C值的方法。现在你将使用所谓的“旧版转换器” - 一种方便的语法,旨在使旧代码移植到Argument Clinic更容易。

    对于每个参数,从 PyArg_Parse() 格式参数复制该参数的“格式单位”,并将 that 指定为其转换器,作为带引号的字符串。 (“format unit”是 format 参数的一到三个字符子串的正式名称,它告诉参数解析函数该变量的类型以及如何转换。有关格式单位的更多信息,请参见 解析参数和构建值。 )

    对于像 z# 这样的多字符格式单元,使用整个2或3个字符串。

    样品:

     /*[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]*/
    
  10. 如果你的函数在格式字符串中有 |,意味着一些参数有默认值,你可以忽略它。 Argument Clinic根据它们是否具有默认值来推断哪些参数是可选的。

    如果您的函数在格式字符串中具有 $,意味着它采用仅关键字参数,则在第一个仅关键字参数之前在一行上指定 *,缩进与参数行相同。

    _pickle.Pickler.dump 既没有,因此我们的样本不变。)

  11. 如果现有的C函数调用 PyArg_ParseTuple() (而不是 PyArg_ParseTupleAndKeywords()),那么它的所有参数都是位置唯一的。

    要在Argument Clinic中将所有参数标记为仅位置,请在最后一个参数后面的行上添加 /,缩进与参数行相同。

    目前这是全无所谓的;或者所有参数都是位置唯一的,或者它们都不是。 (在将来,参考诊所可放松这个限制。)

    样品:

    /*[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]*/
    
  12. 为每个参数编写每个参数的文档字符串是有帮助的。但是每个参数的文档字符串是可选的;如果您愿意,可以跳过此步骤。

    以下是如何添加每个参数的文档字符串。每个参数的文档字符串的第一行必须比参数定义缩进。第一行的左边距为整个每个参数的文档字符串建立左边距;所有你写的文本将被这个金额过多。你可以写多少文本,如果你愿意,跨多行。

    样品:

    /*[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]*/
    
  13. 保存并关闭文件,然后运行 Tools/clinic/clinic.py 就可以了。运气一切工作,你的块现在有输出!在文本编辑器中重新打开文件以查看:

    /*[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]*/
    

    显然,如果Argument Clinic没有生成任何输出,那是因为它在您的输入中发现错误。继续修正错误并重试,直到Argument Clinic处理您的文件,而无需投诉。

  14. 仔细检查参数解析代码Argument Clinic是否与现有代码基本相同。

    首先,确保两个地方都使用相同的参数解析函数。现有代码必须调用 PyArg_ParseTuple()PyArg_ParseTupleAndKeywords();确保由Argument Clinic生成的代码调用 exact 相同的函数。

    第二,传递到 PyArg_ParseTuple()PyArg_ParseTupleAndKeywords() 的格式字符串应该是与现有函数中的手写字符相同的 exactly,直到冒号或分号。

    (Argument Clinic总是使用 : 生成其格式字符串,然后是函数名称,如果现有代码的格式字符串以 ; 结尾,则提供使用帮助,此更改是无害的 - 不必担心)。

    第三,对于格式单元需要两个参数(如长度变量,编码字符串或指向转换函数的指针)的参数,请确保第二个参数 exactly 在两个调用之间相同。

    第四,在块的输出部分,你会发现一个预处理器宏,为这个内置函数定义适当的静态 PyMethodDef 结构:

    #define __PICKLE_PICKLER_DUMP_METHODDEF    \
    {"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__},
    

    这个静态结构应该是与此内置函数的现有静态 PyMethodDef 结构相同的 exactly

    如果这些项目中的任何项目在 任何方式 中不同,请调整您的Argument Clinic功能规范并重新运行 Tools/clinic/clinic.py,直到它们 are 相同。

  15. 请注意,其输出的最后一行是您的“impl”函数的声明。这是内置的实现。删除你修改的函数的原型,但留下开头的大括号。现在删除它的参数解析代码和它转储参数的所有变量的声明。注意Python参数现在是这个impl函数的参数。如果实现对这些变量使用不同的名称,请修复它。

    让我们重申一下,只是因为它很奇怪。您的代码现在应该看起来像这样:

    static return_type
    your_function_impl(...)
    /*[clinic end generated code: checksum=...]*/
    {
    ...
    

    Argument Clinic生成校验和行和上面的函数原型。你应该为函数写入开头(和结束)花括号,以及里面的实现。

    样品:

    /*[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;
    
        ...
    
  16. 记住这个函数的 PyMethodDef 结构的宏吗?找到此函数的现有 PyMethodDef 结构,并将其替换为对宏的引用。 (如果内置在模块范围,这可能会非常接近文件的结尾;如果内置是一个类方法,这可能会在下面但相对接近实现)。

    请注意,宏的正文包含一个尾随逗号。因此,当您使用宏替换现有的静态 PyMethodDef 结构时,don’t 向末尾添加逗号。

    样品:

    static struct PyMethodDef Pickler_methods[] = {
        __PICKLE_PICKLER_DUMP_METHODDEF
        __PICKLE_PICKLER_CLEAR_MEMO_METHODDEF
        {NULL, NULL}                /* sentinel */
    };
    
  17. 编译,然后运行回归测试套件的相关部分。此更改不应引入任何新的编译时警告或错误,并且应该没有外部可见的更改Python的行为。

    嗯,除了一个区别:inspect.signature() 运行你的函数现在应该提供一个有效的签名!

    恭喜,您已将第一个功能移植到Argument Clinic!

高级主题

现在你已经有过使用Argument Clinic的经验,现在是一些高级主题的时候了。

符号默认值

为参数提供的默认值不能是任何任意表达式。目前明确支持以下:

  • 数字常量(整数和浮点数)

  • 字符串常量

  • TrueFalseNone

  • 简单的符号常量,如 sys.maxsize,必须以模块的名称开头

如果你好奇,这是在 Lib/inspect.pyfrom_builtin() 中实现。

(在将来,这可能需要更复杂,允许像 CONSTANT - 1 的完整表达式。)

重命名由Argument Clinic生成的C函数和变量

Argument Clinic自动为其生成的函数命名。有时,如果生成的名称与现有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允许你给一个参数不同的名称在Python和C中,使用相同的 "as" 语法:

/*[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(),但这将很快改变。

可选组

一些遗留函数有一个棘手的方法来解析它们的参数:它们计算位置参数的数量,然后使用 switch 语句根据多少位置参数调用几个不同的 PyArg_ParseTuple() 调用之一。 (这些函数不能接受只有关键字的参数。)此方法用于在创建 PyArg_ParseTupleAndKeywords() 之前模拟可选参数。

虽然使用此方法的函数通常可以转换为使用 PyArg_ParseTupleAndKeywords(),可选参数和默认值,但并不总是可能的。这些遗留功能中的一些具有 PyArg_ParseTupleAndKeywords() 不直接支持的行为。最明显的例子是内建函数 range(),它在所需参数的 left 侧有一个可选参数!另一个例子是 curses.window.addch(),它有一组必须始终一起指定的两个参数。 (参数称为 xy;如果调用在 x 中传递的函数,则还必须传入 y - 如果不传入 x,则也不能传入 y。)

在任何情况下,Argument Clinic的目标是支持对所有现有CPython内置函数的参数解析,而不更改其语义。因此,Argument Clinic使用所谓的 可选组 支持这种替代方法来解析。可选组是必须全部传递在一起的参数组。它们可以位于所需参数的左侧或右侧。他们可以使用 only 与仅位置参数。

注解

可选组是 only,用于转换对 PyArg_ParseTuple() 进行多个调用的函数!使用 any 其他方法解析参数的函数应该 几乎从不 使用可选组转换为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.
    ]
    /

...

笔记:

  • 对于每个可选组,一个附加参数将传递到表示组的impl函数。该参数将是名为 group_{direction}_{number} 的int,其中 {direction}rightleft,这取决于该组是否在所需参数之前或之后,并且 {number} 是单调增加的数字(从1开始),指示该组离开必需参数。当调用impl时,如果此组未使用,则此参数将设置为零,如果使用此组,则将其设置为非零。 (通过使用或未使用,我的意思是参数是否在此调用接收参数。)

  • 如果没有必需的参数,那么可选组将表现为它们在所需参数的右边。

  • 在歧义的情况下,参数解析代码倾向于左侧的参数(在所需的参数之前)。

  • 可选组只能包含仅位置参数。

  • 可选组是用于传统代码的 only。请不要对新代码使用可选组。

使用真正的Argument Clinic转换器,而不是“旧转换器”

为了节省时间,并最大限度地减少您需要学习多少来实现您的第一个端口到Argument Clinic,上面的演练告诉您使用“旧版转换器”。 “传统转换器”是一个方便,明确设计使现有代码移植到Argument Clinic更容易。并且要清楚,当移植代码为Python 3.4时,他们的使用是可以接受的。

然而,从长远来看,我们可能希望所有的块使用Argument Clinic的转换器的真正语法。为什么?有几个原因:

  • 正确的转换器更容易阅读和更清楚的意图。

  • 有些格式单元不支持作为“旧版转换器”,因为它们需要参数,而旧版转换器语法不支持指定参数。

  • 在将来我们可能有一个新的参数解析库,不限于 PyArg_ParseTuple() 支持什么;这种灵活性将不适用于使用旧版转换器的参数。

因此,如果您不介意多少额外的努力,请使用正常的转换器,而不是旧版转换器。

简而言之,Argument Clinic(非传统)转换器的语法看起来像一个Python函数调用。但是,如果函数没有显式参数(所有函数都采用其默认值),则可以省略括号。因此,boolbool() 是完全相同的转换器。

Argument Clinic转换器的所有参数都是仅限关键字的。所有参数临床转换器接受以下参数:

c_default

此参数在C中定义时的默认值。具体来说,这将是在“parse函数”中声明的变量的初始值。请参阅 缺省值部分 了解如何使用它。指定为字符串。

annotation

此参数的注释值。目前不支持,因为PEP 8强制Python库不能使用注释。

此外,一些转换器接受额外的参数。这里是这些参数的列表,以及它们的含义:

accept

一组Python类型(可能是伪类型);这将允许的Python参数限制为这些类型的值。 (这不是通用工具;通常它只支持特定的类型列表,如旧版转换器表中所示)。

要接受 None,请将 NoneType 添加到此集。

bitwise

仅支持无符号整数。此Python参数的原生整数值将写入参数,而不进行任何范围检查,即使对于负值也是如此。

converter

仅由 object 转换器支持。指定用于将此对象转换为本机类型的 C“转换器功能” 的名称。

encoding

仅支持字符串。指定将此字符串从Python str(Unicode)值转换为C char * 值时要使用的编码。

subclass_of

仅支持 object 转换器。需要Python值是Python类型的子类,如C中所示。

type

仅支持 objectself 转换器。指定将用于声明变量的C类型。默认值为 "PyObject *"

zeroes

仅支持字符串。如果为true,则在值内部允许嵌入的NUL字节('\\0')。字符串的长度将被传递到impl函数,紧跟在字符串参数之后,作为名为 <parameter_name>_length 的参数。

请注意,并非每个可能的参数组合都可以工作。通常这些参数由特定的 PyArg_ParseTuple 格式单位 实现,具有特定的行为。例如,目前您不能调用 unsigned_short 而不指定 bitwise=True。虽然这是完全合理的认为这将工作,这些语义不映射到任何现有的格式单位。所以Argument Clinic不支持它。 (或者,至少,还没有。)

下面的表格显示了旧式转换器到真正的Argument Clinic转换器的映射。左边是旧版转换器,右边是您要替换的文本。

'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]*/

Argument Clinic将向您显示其提供的所有转换器。对于每个转换器,它将显示它接受的所有参数,以及每个参数的默认值。只需运行 Tools/clinic/clinic.py --converters 即可查看完整列表。

Py_buffer

当使用 Py_buffer 转换器(或 's*''w*''*y''z*' 旧版转换器)时,must 不会在提供的缓冲区上调用 PyBuffer_Release()。 Argument Clinic会生成代码(在解析函数中)。

高级转换器

记住那些您第一次跳过的格式单元,因为它们是高级的?这里是如何处理这些。

诀窍是,所有这些格式单元都接受参数 - 转换函数,类型或指定编码的字符串。 (但是“旧版转换器”不支持参数,这就是为什么我们跳过它们的第一个函数。)您指定的格式单元的参数现在是转换器的参数;此参数是 converter (对于 O&),subclass_of (对于 O!)或 encoding (对于以 e 开始的所有格式单元)。

使用 subclass_of 时,您可能还需要使用 object() 的其他自定义参数:type,它允许您设置实际用于参数的类型。例如,如果你想确保对象是 PyUnicode_Type 的子类,你可能想使用转换器 object(type='PyUnicodeObject *', subclass_of='&PyUnicode_Type')

使用Argument Clinic的一个可能的问题是:从 e 开始的格式单元可能会有一些可能的灵活性。当用手写一个 PyArg_Parse 调用时,你可以在理论上决定在运行时什么编码字符串传递到 PyArg_ParseTuple()。但是现在这个字符串必须在Argument-Clinic-预处理时被硬编码。这个限制是故意的;它使支持这种格式化单元更容易,并且可以允许未来的优化。这种限制似乎不合理; CPython本身总是为格式单元以 e 开头的参数传递静态硬编码的编码字符串。

参数默认值

参数的默认值可以是多个值中的任何一个。最简单的,它们可以是string,int或float字面量:

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 初始化。

指定为默认值的表达式

参数的默认值不仅仅是文字值。它可以是一个完整的表达式,使用数学运算符和查找对象上的属性。然而,这种支持不是很简单,因为一些非显而易见的语义。

请考虑以下示例:

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_default 参数在C中指定等效表达式:

foo: Py_ssize_t(c_default="PY_SSIZE_T_MAX - 1") = sys.maxsize - 1

另一个并发症:Argument Clinic不能提前知道您提供的表达是否有效。它解析它,以确保它看起来合法,但它不能 actually 知道。当使用表达式来指定在运行时保证有效的值时,必须非常小心!

最后,因为表达式必须可表示为静态C值,所以对法律表达式有很多限制。以下是您不允许使用的Python功能的列表:

  • 函数调用。

  • 内联if语句(3 if foo else 5)。

  • 自动序列解包(*[1, 2, 3])。

  • List/set/dict解释和生成器表达式。

  • Tuple/list/set/dict字面量。

使用返回转换器

默认情况下,impl函数Argument Clinic为你生成返回 PyObject *。但是你的C函数经常计算一些C类型,然后在最后一刻将它转换为 PyObject *。 Argument Clinic处理将您的输入从Python类型转换为本地C类型 - 为什么不将它的返回值从本地C类型转换为Python类型?

这就是一个“返回转换器”。它更改您的impl函数以返回一些C类型,然后将代码添加到生成的(非impl)函数,以处理将该值转换为适当的 PyObject *

返回转换器的语法与参数转换器的语法相似。你指定返回转换器,就像它是一个函数本身的返回注释。返回转换器的行为与参数转换器的行为相同;他们接受参数,参数都是仅关键字,如果你不改变任何默认参数,你可以省略括号。

(如果您使用 "as" and 作为函数的返回转换器,则 "as" 应该在返回转换器之前。)

使用返回转换器时还有一个额外的复杂因素:如何指示发生错误?通常,函数返回有效(非 NULL)指针用于成功,以及 NULL 用于失败。但是如果你使用一个整数返回转换器,所有的整数都是有效的。如何Argument Clinic检测到错误?它的解决方案:每个返回转换器隐含地查找指示错误的特殊值。如果返回该值,并且已设置错误(PyErr_Occurred() 返回true值),则生成的代码将传播错误。否则它将编码你返回的值像正常。

目前Argument Clinic仅支持几个返回转换器:

bool
int
unsigned int
long
unsigned int
size_t
Py_ssize_t
float
double
DecodeFSDefault

这些都不带参数。对于前三个,返回-1表示错误。对于 DecodeFSDefault,返回类型为 char *;返回NULL指针以指示错误。

(还有一个实验性的 NoneType 转换器,它可以让您在成功时返回 Py_None 或在失败时返回 NULL,而不必增加 Py_None 上的引用计数,我不确定是否增加了足够的清晰度值得使用。

要查看Argument Clinic支持的所有返回转换器及其参数(如果有),只需运行 Tools/clinic/clinic.py --converters 即可查看完整列表。

克隆现有函数

如果您有许多类似的功能,您可以使用Clinic的“克隆”功能。克隆现有函数时,可以重用:

  • 其参数,包括

    • 他们的名字,

    • 其转换器,具有所有参数,

    • 其默认值,

    • 他们的每个参数的文档字符串,

    • 他们的 kind (无论它们只是位置,位置或关键字,或仅关键字),和

  • 其返回转换器。

唯一没有从原始函数复制的是它的docstring;语法允许您指定一个新的docstring。

下面是克隆函数的语法:

/*[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,以说明必须使用 both 函数的完整路径。)

对不起,没有用于部分克隆函数或克隆函数然后修改它的语法。克隆是一个全有或无关的命题。

此外,您要克隆的函数必须已在当前文件中预先定义。

调用Python代码

其余的高级主题需要你编写Python代码,它居住在C文件中,并修改Argument Clinic的运行时状态。这很简单:你只需定义一个Python块。

Python块使用不同于Argument Clinic功能块的分隔符行。它看起来像这样:

/*[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会使用默认转换器为您自动添加“self”参数。它自动将此参数的 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() “转换器函数”。

你的转换器类应该命名为 *something*_converter。如果名称遵循这个约定,那么你的转换器类将自动注册到Argument Clinic;它的名称将是您的类的名称,_converter 后缀被剥离。 (这是用元类完成的。)

你不应该子类 CConverter.__init__。相反,你应该写一个 converter_init() 函数。 converter_init() 总是接受 self 参数;之后,所有附加参数 must 都是仅关键字的。在Argument Clinic中传递给转换器的任何参数都将传递到您的 converter_init()

还有一些 CConverter 的其他成员,您可能希望在您的子类中指定。以下是当前列表:

type

用于此变量的C类型。 type 应该是一个指定类型的Python字符串,例如。 int。如果这是一个指针类型,则类型字符串应以 ' *' 结尾。

default

此参数的Python默认值,为Python值。或者魔法值 unspecified 如果没有默认值。

py_default

default,因为它应该在Python代码中显示为字符串。或 None 如果没有默认值。

c_default

default,因为它应该出现在C代码中,作为字符串。或 None 如果没有默认值。

c_ignored_default

用于在没有默认值但未指定默认值时初始化C变量的默认值可能会导致“未初始化变量”警告。这在使用选项组时很容易发生 - 虽然正确编写的代码永远不会使用这个值,变量被传递到impl,而C编译器将抱怨未初始化值的“使用”。此值应始终为非空字符串。

converter

C转换器函数的名称,作为字符串。

impl_by_reference

布尔值。如果为true,Argument Clinic在将变量传递给impl函数时,会在变量名前面添加一个 &

parse_by_reference

布尔值。如果为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]*/

此块向Argument Clinic添加名为 ssize_t 的转换器。声明为 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_newtp_init 功能。只要将它们命名为 __new____init__ 即可。笔记:

  • __new__ 生成的函数名称在 __new__ 中不会像默认情况下那样结束。它只是类的名称,转换为有效的C标识符。

  • 没有为这些功能生成 PyMethodDef #define

  • __init__ 函数返回 int,而不是 PyObject *

  • 使用docstring作为类docstring。

  • 虽然 __new____init__ 函数必须始终接受 argskwargs 对象,但转换时可以为这些函数指定任何您喜欢的签名。 (如果你的函数不支持关键字,生成的解析函数会抛出一个异常,如果它收到任何。

更改和重定向Clinic的输出

将Clinic的输出与您常规的手工编辑的C代码交织在一起可能不方便。幸运的是,Clinic是可配置的:您可以缓冲其输出以便以后打印(或更早的!),或将其输出写入单独的文件。您还可以为Clinic生成的输出的每一行添加前缀或后缀。

以这种方式更改Clinic的输出可能是一个可读性,它可能导致Clinic代码使用类型之前,它们被定义,或者您的代码试图使用Clinic生成的代码,在定义之前。这些问题可以通过重新排列文件中的声明,或移动Clinic生成的代码的位置来轻松解决。 (这就是为什么Clinic的默认行为是将所有内容输出到当前块中;虽然许多人认为这会妨碍可读性,但它不会需要重新安排代码来修复定义之前的问题。)

让我们开始定义一些术语:

field

在本上下文中,字段是Clinic输出的子部分。例如,PyMethodDef 结构的 #define 是一个称为 methoddef_define 的字段。诊所有七个不同的字段,它可以输出每个函数定义:

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

默认目标:打印在当前Clinic块的输出部分。

buffer

一个文本缓冲区,您可以在其中保存文本供以后使用。此处发送的文本将追加到任何现有文本的末尾。当Clinic完成处理文件时,在缓冲区中保留任何文本是一个错误。

file

将由Clinic自动创建的单独的“诊所文件”。为文件选择的文件名为 {basename}.clinic{extension},其中 basenameextension 分配了当前文件上运行的 os.path.splitext() 的输出。 (例如:_pickle.cfile 目标将写入 _pickle.clinic.c。)

重要:使用a file 目的地,你 必须检入 生成的文件!

two-pass

buffer 的缓冲区。但是,双通道缓冲区只能写入一次,并且打印出在所有处理过程中发送给它的所有文本,即使是从临床块 after

suppress

文本被抑制 - 抛弃。

Clinic定义了五个新的指令,让您重新配置其输出。

第一个新指令是 dump:

dump <destination>

这将命名目标的当前内容转储到当前块的输出中,并清空它。这只适用于 buffertwo-pass 目的地。

第二个新指令是 outputoutput 的最基本形式是这样的:

output <field> <destination>

这告诉诊所输出 fielddestinationoutput 还支持一个特殊的元目的地,称为 everything,它告诉Clinic输出 all 字段给 destination

output 有许多其他功能:

output push
output pop
output preset <preset>

output pushoutput pop 允许您在内部配置堆栈上推送和弹出配置,以便您可以临时修改输出配置,然后轻松恢复以前的配置。只需在更改之前按下即可保存当前配置,然后在希望还原以前的配置时弹出。

output preset 将Clinic的输出设置为以下几种内置预设配置之一:

block

诊所的原始启动配置。在输入块之后立即写入所有内容。

抑制 parser_prototypedocstring_prototype,将一切写入 block

file

旨在将一切写入“诊所文件”,它可以。然后 #include 这个文件在文件的顶部附近。你可能需要重新安排你的文件以使这项工作,虽然通常这只是意味着创建向前声明各种 typedefPyTypeObject 定义。

抑制 parser_prototypedocstring_prototype,将 impl_definition 写入 block,并将一切写入 file

默认文件名为 "{dirname}/clinic/{basename}.h"

buffer

保存Clinic的大部分输出,在结束附近写入您的文件。对于实现模块或内置类型的Python文件,建议将缓冲区转储到模块或内置类型的静态结构上方;这些通常非常接近结束。如果文件具有在文件中间定义的静态 PyMethodDef 数组,则使用 buffer 可能需要比 file 更多的编辑。

抑制 parser_prototypeimpl_prototypedocstring_prototype,将 impl_definition 写入 block,并将一切写入 file

two-pass

类似于 buffer 预设,但将向前声明写入 two-pass 缓冲区,并将定义写入 buffer。这类似于 buffer 预设,但可能需要比 buffer 更少的编辑。将 two-pass 缓冲区转储到文件顶部附近,并将 buffer 转储到结束处,就像使用 buffer 预设时一样。

抑制 impl_prototype,将 impl_definition 写入 block,将 docstring_prototypemethoddef_defineparser_prototype 写入 two-pass,将一切写入 buffer

partial-buffer

buffer 预设类似,但是将更多的东西写入 block,只将生成的代码的真正大块写入 buffer。这完全避免了 buffer 的使用前定义问题,以在块的输出中具有稍多的填充的小成本。转储 buffer 接近结束,就像你在使用 buffer 预设时。

抑制 impl_prototype,将 docstring_definitionparser_definition 写入 buffer,将其他内容写入 block

第三个新指令是 destination:

destination <name> <command> [...]

这将对名为 name 的目标执行操作。

有两个定义的子命令:newclear

new 子命令的工作方式如下:

destination <name> new <type>

这将创建一个名为 <name> 和类型 <type> 的新目标。

有五种目的地类型:

suppress

把文本拖走。

block

将文本写入当前块。这是Clinic最初做的。

buffer

一个简单的文本缓冲区,就像上面的“缓冲区”内置目的地。

file

文本文件。文件目标需要一个额外的参数,一个用于构建文件名的模板,如下所示:

destination <name> new <type> <file_template>

模板可以在内部使用三个字符串,将由文件名的位替换:

{路径}

文件的完整路径,包括目录和完整文件名。

{dirname}

文件所在目录的名称。

{basename}

只是文件的名称,不包括目录。

{basename_root}

带有扩展名的基本名称(所有内容都包括但不包括最后一个’。’)。

{basename_extension}

最后 ‘。’和之后的一切。如果基本名称不包含句点,这将是空字符串。

如果文件名中没有句点,{basename}和{filename}是相同的,并且{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输出的每一行的字符串。

这两个都支持两个格式字符串:

{block comment start}

变成字符串 /*,C文件的开始 - 注释文本序列。

{block comment end}

变成字符串 */,C文件的结束注释文本序列。

最后的新指令是你不应该直接使用,称为 preserve:

preserve

这告诉Clinic,输出的当前内容应该保持不变。当将输出转储到 file 文件中时,Clinic在内部使用它;将其包装在Clinic块中可以使Clinic使用其现有的校验和功能,以确保文件在被覆盖之前不被手动修改。

#ifdef技巧

如果你正在转换一个不是在所有平台上都可用的功能,你可以使用一个技巧,使生活更容易一些。现有代码可能看起来像这样:

#ifdef HAVE_FUNCTIONNAME
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */

然后在 PyMethodDef 结构底部现有的代码就会有:

#ifdef HAVE_FUNCTIONNAME
{'functionname', ... },
#endif /* HAVE_FUNCTIONNAME */

在这种情况下,你应该在 #ifdef 中包含你的impl函数的主体,像这样:

#ifdef HAVE_FUNCTIONNAME
/*[clinic input]
module.functionname
...
[clinic start generated code]*/
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */

然后,从 PyMethodDef 结构中删除这三行,将其替换为宏参数临床生成的:

MODULE_FUNCTIONNAME_METHODDEF

(你可以在生成的代码中找到这个宏的真实名称,或者你可以自己计算它:它是在你的块的第一行定义的函数的名字,但是句点改为下划线,大写和 "_METHODDEF" 添加到最后。)

也许你在想:如果 HAVE_FUNCTIONNAME 没有定义? MODULE_FUNCTIONNAME_METHODDEF 宏也不会被定义!

这里是Argument Clinic非常聪明的地方。它实际上检测到Argument Clinic块可能被 #ifdef 停用。当这种情况发生时,它会生成一些额外的代码,看起来像这样:

#ifndef MODULE_FUNCTIONNAME_METHODDEF
    #define MODULE_FUNCTIONNAME_METHODDEF
#endif /* !defined(MODULE_FUNCTIONNAME_METHODDEF) */

这意味着宏总是工作。如果函数被定义,这将变成正确的结构,包括结尾的逗号。如果函数未定义,则变为无。

然而,这导致一个痒的问题:当使用“块”输出预设时,Argument Clinic应该在这个额外的代码?它不能进入输出块,因为它可以被 #ifdef 停用。 (这是整个点!)

在这种情况下,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预处理器!

由于Python注释不同于C注释,嵌入在Python文件中的Argument Clinic块看起来略有不同。他们看起来像这样:

#/*[python input]
#print("def foo(): pass")
#[python start generated code]*/
def foo(): pass
#/*[python checksum:...]*/