【问题标题】:Add a signature, with annotations, to extension methods将带有注释的签名添加到扩展方法
【发布时间】:2018-05-25 22:05:14
【问题描述】:

在我的应用程序中嵌入 Python 并编写扩展类型时,我可以使用正确制作的 .tp_doc 字符串将 signature 添加到方法中。

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 库不能使用注释。

【问题讨论】:

  • 首先,您能否切换到使用Argument Clinic(或者使用更高级的语言,如 C++ 或 Rust,它们具有很好的包装器,可以更轻松地为您生成所有样板文件)?
  • 我相信如果你手动完成这一切,你不能在PyMethodDef中以声明方式进行,你必须构造函数然后调用PyFunction_SetAnnotations或类似的东西结果,但老实说,我只会将原始 C API 用于添加注释会过大的琐碎事情,所以我不知道。
  • @abarnert 我将不得不寻找那些 C++ 包装器。这是一个 C++ 项目,我切换到用于 python C-API 的 C 文件,所以我可以在声明 PyTypeObjects 时使用 { .tp_doc = "..." } 命名结构初始化器语法。 (捂脸)除了 Argument Clinic,我也会调查PyFunction_SetAnnotations。谢谢。

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


【解决方案1】:

TL;DR 目前没有办法做到这一点。

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

理论上它是这样工作的(对于 Python C 扩展对象):

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

如果您使用参数诊所,则无需自己编写“正确的文档字符串”。签名行是根据代码中的 cmets 生成的。但是,前面提到的 2 个步骤仍然会发生。它们只是发生在自动生成的签名行

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

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

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

注解有什么问题?

注解有几个问题:

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

    举个例子,如果你使用这个“签名”:

    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 会起作用吗?

在 cmets 中提到了这个 C API 函数。但是,这故意不适用于 C 扩展函数。如果您尝试在 C 扩展函数上调用它,它将引发 SystemError: bad argument to internal function call。我用一个小的 Cython Jupyter“脚本”对此进行了测试:

%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 模块,所以它可能是可行的,但我还没有进行概念验证,所以把它当作一个可能的可能,但可能非常复杂,几乎可以肯定不值得麻烦

但是,您始终可以将 C 扩展函数包装为 Python 函数(只是一个包装器)。这个 Python 包装器可以有函数注释。它需要更多的维护和一点点慢,但可以为您省去签名和 C 扩展的所有麻烦。我不太确定,但如果你使用 Cython 来包装你的 C 或 C++ 代码,它甚至可能有一些自动化工具(自动编写 Python 包装器)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多