添加带注释的签名到扩展方法

时间:2018-05-25 22:05:15

标签: python python-3.6 signature python-c-api python-internals

在我的应用程序中嵌入Python并编写扩展类型时,我可以使用经过精心设计的signature字符串向该方法添加.tp_doc

static PyMethodDef Answer_methods[] = {
  { "ultimate", (PyCFunction)Answer_ultimate, METH_VARARGS, 
    "ultimate(self, question='Life, the universe, everything!')\n"
    "--\n"
    "\n"
    "Return the ultimate answer to the given question." },
  { NULL }
};

执行help(Answer)时,返回以下内容(缩写):

class Answer(builtins.object)
 |
 |  ultimate(self, question='Life, the universe, everything!')
 |      Return the ultimate answer to the given question.

这很好,但我使用的是Python3.6,它支持注释。我想将问题注释为字符串,以及返回int的函数。我试过了:

static PyMethodDef Answer_methods[] = {
  { "ultimate", (PyCFunction)Answer_is_ultimate, METH_VARARGS, 
    "ultimate(self, question:str='Life, the universe, everything!') -> int\n"
    "--\n"
    "\n"
    "Return the ultimate answer to the given question." },
  { NULL }
};

但这会恢复为(...)表示法,文档变为:

 |  ultimate(...)
 |      ultimate(self, question:str='Life, the universe, everything!') -> int
 |      --
 |
 |      Return the ultimate answer to the given question.

并要求inspect.signature(Answer.ultimate)会导致异常。

Traceback (most recent call last):
  File "<string>", line 11, in <module>
  File "inspect.py", line 3037, in signature
  File "inspect.py", line 2787, in from_callable
  File "inspect.py", line 2266, in _signature_from_callable
  File "inspect.py", line 2090, in _signature_from_builtin
ValueError: no signature found for builtin <built-in method ultimate of example.Answer object at 0x000002179F3A11B0>

我尝试用Python代码添加注释:

example.Answer.ultimate.__annotations__ = {'return': bool}

但是内置方法描述符不能以这种方式添加注释。

Traceback (most recent call last):
  File "<string>", line 2, in <module>
AttributeError: 'method_descriptor' object has no attribute '__annotations__'

有没有办法使用C-API为扩展方法添加注释?

Argument Clinic看起来很有前景,可能仍然非常有用,但截至3.6.5,doesn't support annotations

  

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

1 个答案:

答案 0 :(得分:1)

TL; DR 目前无法做到这一点。

签名和C扩展如何协同工作?

理论上它的工作原理如下(对于Python C扩展对象):

  • 如果C函数具有&#34;正确的文档字符串&#34;签名存储在__text_signature__属性中。
  • 如果您在此类对象上调用helpinspect.signature,则会解析__text_signature__并尝试从中构建签名。

如果您使用参数诊所,则不需要编写&#34;正确的文档字符串&#34;你自己。签名行是根据代码中的注释生成的。然而,前面提到的两个步骤仍然发生。它们只是发生在到自动生成的签名行

这就是为什么像sum这样的内置Python函数有__text-signature__ s的原因:

>>> sum.__text_signature__
'($module, iterable, start=0, /)'

此案例中的签名是通过基于on the comments around the sum implementation的参数诊所生成的。

注释有什么问题?

注释有几个问题:

  • 返回注释会破坏&#34;正确的文档字符串&#34;的合同。因此,当您添加返回注释时,__text_signature__将为空。这是一个主要问题,因为解决方法必然会涉及重写负责文档字符串的CPython C代码部分 - &gt; __text_signature__翻译!这不仅复杂,而且您还必须提供已更改的CPython版本,以便它适用于使用您的功能的人。

    就像例如,如果你使用这个&#34;签名&#34;:

    ultimate(self, question:str='Life, the universe, everything!') -> int
    

    你得到:

    >>> ultimate.__text_signature__ is None
    True
    

    但是如果你删除了返回注释:

    ultimate(self, question:str='Life, the universe, everything!')
    

    它会为您提供__text_signature__

    >>> ultimate.__text_signature__
    "(self, question:str='Life, the universe, everything!')"
    
  • 如果您没有返回注释,它仍然无法正常工作,因为明确不支持(当前)注释。

    假设你有这个签名:

    ultimate(self, question:str='Life, the universe, everything!')
    

    它不能与inspect.signature一起使用(异常消息实际上说明了所有内容):

    >>> import inspect
    >>> inspect.signature(ultimate)
    Traceback (most recent call last):
    ...
        raise ValueError("Annotations are not currently supported")
    ValueError: Annotations are not currently supported
    

    负责解析__text_signature__的函数是inspect._signature_fromstr。从理论上讲,你可能可能可以通过猴子修补它来使它工作(返回注释仍然无法工作!)。但也许不是,有几个地方对__text_signature__做出假设,可能不适用于注释。

PyFunction_SetAnnotations会工作吗?

在评论中提到了这个C API函数。然而,这故意不适用于C扩展功能。如果您尝试在C扩展函数上调用它,它将引发SystemError: bad argument to internal function call。我用一个小的Cython Jupyter&#34;脚本测试了这个&#34;:

%load_ext cython

%%cython

cdef extern from "Python.h":
    bint PyFunction_SetAnnotations(object func, dict annotations) except -1

cpdef call_PyFunction_SetAnnotations(object func, dict annotations):
    PyFunction_SetAnnotations(func, annotations)

>>> call_PyFunction_SetAnnotations(sum, {})

---------------------------------------------------------------------------
SystemError                               Traceback (most recent call last)
<ipython-input-4-120260516322> in <module>()
----> 1 call_PyFunction_SetAnnotations(sum, {})

SystemError: ..\Objects\funcobject.c:211: bad argument to internal function

因此,它也不适用于C扩展函数。

摘要

所以返回注释目前是完全不可能的(至少没有在程序中分发你自己的CPython)。如果您在inspect模块中修补私有函数,则参数注释可以工作。它是一个Python模块,所以它可以可行,但我还没有做出概念验证,所以把它视为可能是可能的,但可能非常复杂,几乎肯定不值得麻烦

但是你总是可以用Python函数包装C扩展函数(只是一个非常包装的东西)。这个Python包装器可以有函数注释。它的维护速度更快,速度更慢,但是使用签名和C扩展可以省去所有麻烦。我不完全确定,但是如果你使用Cython包装你的C或C ++代码,它甚至可能有一些自动化工具(自动编写Python包装器)。