Python C扩展:如何记录方法签名?

16 投票
3 回答
3647 浏览
提问于 2025-04-15 12:47

我正在写C语言扩展,想让我的方法签名在查看时能被看到。

static PyObject* foo(PyObject *self, PyObject *args) {

    /* blabla [...] */

}

PyDoc_STRVAR(
    foo_doc,
    "Great example function\n"
    "Arguments: (timeout, flags=None)\n"
    "Doc blahblah doc doc doc.");

static PyMethodDef methods[] = {
    {"foo", foo, METH_VARARGS, foo_doc},
    {NULL},
};

PyMODINIT_FUNC init_myexample(void) {
    (void) Py_InitModule3("_myexample", methods, "a simple example module");
}

现在,如果我在构建完成后加载这个模块,然后查看它的帮助信息:

>>> import _myexample
>>> help(_myexample)

我会看到:

Help on module _myexample:

NAME
    _myexample - a simple example module

FILE
    /path/to/module/_myexample.so

FUNCTIONS
    foo(...)
        Great example function
        Arguments: (timeout, flags=None)
        Doc blahblah doc doc doc.

我希望能更具体一些,把foo(...)替换成foo(timeout, flags=None)

我能做到这一点吗?怎么做呢?

3 个回答

0

如果这对你有帮助,我发现下面这样的写法可以提供一个类型签名,并且让PyCharm理解这些类型。

对于一个应该有以下签名的函数(第二个参数是可选的):

def function(paramA: str, paramB: bool = False) -> str:

我会在PyMethodDef的ml_doc字段中使用以下内容:

"function(paramA, /, paramB=False)\n--\nn"
"function(paramA: str, paramB: bool=False) -> str\n"
":param str paramA: blah blah blah\n"
":param str paramB: blah blah blah\n"
":returns: blah blah blah\n"
":rtype: str\n\n"
"some narrative description of the function"

在"\n--\n\n"之前的所有内容都是用来让Python填充__text_signature__这个方法的。剩下的内容则放进__doc__里。PyCharm会解析__doc__来判断类型。:rtype:这一行末尾的双换行是必要的,这样PyCharm才能正确处理,否则后面的描述会被附加到这一行上。

20

已经过去7年了但你可以为C扩展函数和类添加签名

Python本身使用Argument Clinic来动态生成签名。然后一些机制会创建一个__text_signature__,这个可以被查看(比如用help命令)。@MartijnPieters在这个回答中很好地解释了这个过程。

你其实可以从Python中获取参数诊所,并以动态的方式来做,但我更喜欢手动的方法:把签名添加到文档字符串中:

在你的情况下:

PyDoc_STRVAR(
    foo_doc,
    "foo(timeout, flags=None, /)\n"
    "--\n"
    "\n"
    "Great example function\n"
    "Arguments: (timeout, flags=None)\n"
    "Doc blahblah doc doc doc.");

我在我的包中大量使用了这个:iteration_utilities/src。为了演示它是有效的,我使用了这个包中暴露的一个C扩展函数:

>>> from iteration_utilities import minmax
>>> help(minmax)
Help on built-in function minmax in module iteration_utilities._cfuncs:

minmax(iterable, /, key, default)
    Computes the minimum and maximum values in one-pass using only
    ``1.5*len(iterable)`` comparisons. Recipe based on the snippet
    of Raymond Hettinger ([0]_) but significantly modified.

    Parameters
    ----------
    iterable : iterable
        The `iterable` for which to calculate the minimum and maximum.
[...]

这个函数的文档字符串在这个文件中定义。

重要的是要意识到在Python 3.4之前这是不可能的,而且你需要遵循一些规则:

  • 你需要在签名定义行后面加上--\n\n

  • 签名必须在文档字符串的第一行。

  • 签名必须是有效的,比如foo(a, b=1, c)是不行的,因为在有默认值的参数后面不能再定义位置参数。

  • 你只能提供一个签名。所以如果你使用类似下面的方式就不行:

    foo(a)
    foo(x, a, b)
    --
    
    Narrative documentation
    
6

我通常处理这类问题的方法是:“查看源代码”。

简单来说,我会假设Python的标准模块在有这种功能时会使用它。查看源代码(比如这里)应该会有帮助,但实际上即使是标准模块也会在自动输出后添加原型。就像这样:

torsten@pulsar:~$ python2.6
>>> import fcntl
>>> help(fcntl.flock)
flock(...)
    flock(fd, operation)

    Perform the lock operation op on file descriptor fd.  See the Unix [...]

所以既然上游(指Python的开发者)没有使用这种功能,我就认为它是不存在的。:-)

好吧,我刚刚检查了当前的python3k源代码,情况还是这样。这个签名是在Python源代码中的 pydoc.py 文件里生成的,具体位置在这里:pydoc.py。相关的摘录从第1260行开始:

        if inspect.isfunction(object):
            args, varargs, varkw, defaults = inspect.getargspec(object)
            ...
        else:
            argspec = '(...)'

inspect.isfunction 用来检查请求文档的对象是否是一个Python函数。但用C语言实现的函数被认为是内置函数,因此你总是会得到 name(...) 作为输出。

撰写回答