向扩展方法添加带有注释的签名
Add a signature, with annotations, to extension methods
在我的应用程序中嵌入 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)
时,returned(缩写)如下:
class Answer(builtins.object)
|
| ultimate(self, question='Life, the universe, everything!')
| Return the ultimate answer to the given question.
这很好,但我使用的是 Python3.6,它支持注释。我想将问题注释为字符串,将函数注释为 return 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
The annotation value for this parameter. Not currently supported, because PEP 8 mandates that the Python library may not use annotations.
TL;DR 目前 无法做到这一点。
签名和 C 扩展如何协同工作?
理论上它是这样工作的(对于 Python C 扩展对象):
- 如果 C 函数具有 "correct docstring",则签名存储在
__text_signature__
属性中。
- 如果您在此类对象上调用
help
或 inspect.signature
,它会解析 __text_signature__
并尝试从中构建签名。
如果您使用参数 clinic,则无需自己编写 "correct docstring"。签名行是根据代码中的注释生成的。然而,前面提到的 2 个步骤仍然会发生。它们恰好自动生成的签名行。
这就是为什么像 sum
这样的内置 Python 函数有一个 __text-signature__
s:
>>> sum.__text_signature__
'($module, iterable, start=0, /)'
本例中的签名是通过基于on the comments around the sum
implementation.
的参数clinic生成的
注释有什么问题?
注解有几个问题:
Return 注释破坏了 "correct docstring" 的约定。因此,当您添加 return 注释时,__text_signature__
将为空。这是一个主要问题,因为解决方法必然涉及重写负责文档字符串 -> __text_signature__
翻译的 CPython C 代码部分!这不仅复杂,而且您还必须提供更改后的 CPython 版本,以便它适用于使用您的函数的人。
举个例子,如果你使用这个 "signature":
ultimate(self, question:str='Life, the universe, everything!') -> int
你得到:
>>> ultimate.__text_signature__ is None
True
但是如果你删除 return 注释:
ultimate(self, question:str='Life, the universe, everything!')
它给你 __text_signature__
:
>>> ultimate.__text_signature__
"(self, question:str='Life, the universe, everything!')"
如果您没有 return 注释,它仍然无法工作,因为明确不支持注释(当前)。
假设你有这个签名:
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
。从理论上讲,您 可能 可以通过猴子修补使其工作(return 注释仍然无法工作!)。但也许不是,有几个地方对可能不适用于注释的 __text_signature__
做出假设。
PyFunction_SetAnnotations
行得通吗?
在评论中提到了这个 C API 函数。然而,这故意不适用于 C 扩展函数。如果您尝试在 C 扩展函数上调用它,它将引发 SystemError: bad argument to internal function call
。我用一个小的 Cython Jupyter "script":
测试了这个
%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 扩展函数。
总结
所以 return 注释目前是完全不可能的(至少在不随程序分发你自己的 CPython 的情况下)。如果您对 inspect
模块中的私有函数进行猴子修补,则参数注释 可以 起作用。这是一个 Python 模块,所以它 可能 是可行的,但我还没有进行概念验证,所以将其视为 可能,但是可能非常复杂,几乎肯定不值得这么麻烦。
但是,您始终可以使用 Python 函数(只是一个非常好的包装器)来包装 C 扩展函数。这个 Python 包装器可以有函数注释。它需要更多维护并且速度稍慢,但可以为您省去签名和 C 扩展的所有麻烦。我不太确定,但如果你使用 Cython 来包装你的 C 或 C++ 代码,它甚至可能有一些自动化工具(自动编写 Python 包装器)。
在我的应用程序中嵌入 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)
时,returned(缩写)如下:
class Answer(builtins.object)
|
| ultimate(self, question='Life, the universe, everything!')
| Return the ultimate answer to the given question.
这很好,但我使用的是 Python3.6,它支持注释。我想将问题注释为字符串,将函数注释为 return 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
The annotation value for this parameter. Not currently supported, because PEP 8 mandates that the Python library may not use annotations.
TL;DR 目前 无法做到这一点。
签名和 C 扩展如何协同工作?
理论上它是这样工作的(对于 Python C 扩展对象):
- 如果 C 函数具有 "correct docstring",则签名存储在
__text_signature__
属性中。 - 如果您在此类对象上调用
help
或inspect.signature
,它会解析__text_signature__
并尝试从中构建签名。
如果您使用参数 clinic,则无需自己编写 "correct docstring"。签名行是根据代码中的注释生成的。然而,前面提到的 2 个步骤仍然会发生。它们恰好自动生成的签名行。
这就是为什么像 sum
这样的内置 Python 函数有一个 __text-signature__
s:
>>> sum.__text_signature__
'($module, iterable, start=0, /)'
本例中的签名是通过基于on the comments around the sum
implementation.
注释有什么问题?
注解有几个问题:
Return 注释破坏了 "correct docstring" 的约定。因此,当您添加 return 注释时,
__text_signature__
将为空。这是一个主要问题,因为解决方法必然涉及重写负责文档字符串 ->__text_signature__
翻译的 CPython C 代码部分!这不仅复杂,而且您还必须提供更改后的 CPython 版本,以便它适用于使用您的函数的人。举个例子,如果你使用这个 "signature":
ultimate(self, question:str='Life, the universe, everything!') -> int
你得到:
>>> ultimate.__text_signature__ is None True
但是如果你删除 return 注释:
ultimate(self, question:str='Life, the universe, everything!')
它给你
__text_signature__
:>>> ultimate.__text_signature__ "(self, question:str='Life, the universe, everything!')"
如果您没有 return 注释,它仍然无法工作,因为明确不支持注释(当前)。
假设你有这个签名:
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
。从理论上讲,您 可能 可以通过猴子修补使其工作(return 注释仍然无法工作!)。但也许不是,有几个地方对可能不适用于注释的__text_signature__
做出假设。
PyFunction_SetAnnotations
行得通吗?
在评论中提到了这个 C API 函数。然而,这故意不适用于 C 扩展函数。如果您尝试在 C 扩展函数上调用它,它将引发 SystemError: bad argument to internal function call
。我用一个小的 Cython Jupyter "script":
%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 扩展函数。
总结
所以 return 注释目前是完全不可能的(至少在不随程序分发你自己的 CPython 的情况下)。如果您对 inspect
模块中的私有函数进行猴子修补,则参数注释 可以 起作用。这是一个 Python 模块,所以它 可能 是可行的,但我还没有进行概念验证,所以将其视为 可能,但是可能非常复杂,几乎肯定不值得这么麻烦。
但是,您始终可以使用 Python 函数(只是一个非常好的包装器)来包装 C 扩展函数。这个 Python 包装器可以有函数注释。它需要更多维护并且速度稍慢,但可以为您省去签名和 C 扩展的所有麻烦。我不太确定,但如果你使用 Cython 来包装你的 C 或 C++ 代码,它甚至可能有一些自动化工具(自动编写 Python 包装器)。