起因:https://ask.csdn.net/questions/7680299
回答
根据math的帮助字符转提示,猜测python的math函数应该是调用的C函数库。
使用ide的话,点击函数跳转到math中,会发现在 math.pyi 文件中,pow函数只定义了函数头,没有具体实现(以下截图通过vs code打开)。
而结合 math.doc 的提示,我们可以猜测math中的函数调用自C库。https://github.com/python/cpython/blob/3.10/Doc/library/math.rst
在stdlib的docs中:https://docs.python.org/zh-cn/3/tutorial/stdlib.html,也说到“math 模块提供对浮点数学的底层C库函数的访问”
另外,python本就支持
**
运算,在 https://docs.python.org/zh-cn/3/library/math.html 中,说到 math.pow 与内置的**
运算符不同。
进一步探究
用 pycharm 试着打开了以下 math.pow 的源码,函数注释 real signature unknown
在StackOverflow查阅该注释的意思,大致观点为:
math是二进制格式的(其他语言实现的库)pycharm无法还原出原始代码,只给出了函数头。
也就是说,对于某些python库,没有用python编写的源代码。这些库是用C或其他语言编写的。
相关参考链接:
为什么函数只有头部,没有具体实现:https://stackoverflow.com/questions/38384206/why-do-some-built-in-python-functions-only-have-pass
如何查找内置 Python 函数的源代码:https://stackoverflow.com/questions/8608587/finding-the-source-code-for-built-in-python-functions
python官方提供的C函数库github:https://github.com/python/cpython/blob/v3.7.2/Modules/mathmodule.c
在官方给的C源码中查找pow的具体实现:
在 https://github.com/python/cpython/blob/v3.7.2/Modules/clinic/mathmodule.c.h 中定义了一个宏 MATH_POW_METHODDEF 。并且math_pow函数应该就是我们要找的 pow 函数的具体实现。
而在 https://github.com/python/cpython/blob/v3.7.2/Modules/mathmodule.c 文件中,该宏写入了 math_methods[] 这个静态数组中。
基于以上信息,我们猜测 pow 的具体实现就是 math_pow 函数, 而通过 math_methods[]
数组将 math_pow的函数绑定到 pow 上提供到 python中。
为了验证以上猜想,我们需要弄清楚C/C++扩展python的语法规则。
注:通过了解c/c++编写python扩展的规则,发现我们的猜想是正确的
关于《使用c/c++编写python扩展》
参考知乎:https://zhuanlan.zhihu.com/p/106411447
官方文档:https://docs.python.org/zh-cn/3/extending/extending.html
第一步:c/c++扩展都要引用这个头文件
#define PY_SSIZE_T_CLEAN
#include <Python.h>
第二步:定义可供python解释器调用的函数
返回值类型为PyObject的静态指针。
// https://github.com/python/cpython/blob/v3.7.2/Modules/clinic/mathmodule.c.h: 313
static PyObject *
math_pow(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
double x;
double y;
if (!_PyArg_ParseStack(args, nargs, "dd:pow",
&x, &y)) {
goto exit;
}
return_value = math_pow_impl(module, x, y);
exit:
return return_value;
}
第三步:定义模块方法表
定义好函数后,我们需要把函数声明为可以被Python调用,因此需要定义一个方法表 “method table” (PyMethodDef 类型静态数组)。然后将该函数放入一个PyMethodDef类型的静态数组中。
// https://github.com/python/cpython/blob/v3.7.2/Modules/mathmodule.c: 2323
static PyMethodDef math_methods[] = {
{
"acos", math_acos, METH_O, math_acos_doc},
/* 省略若干 */
// ..
MATH_POW_METHODDEF // 该宏在clinic/mathmodule.c.h 307行定义,展开如下:
/*#define MATH_POW_METHODDEF \
{"pow", (PyCFunction)math_pow, METH_FASTCALL, math_pow__doc__}, */
{
NULL, NULL} /* sentinel */
这样一个静态变量数组可以存放多个函数的定义,并且以NULL定义作为结尾。
对于数组的每个元素,有四个成员变量,分别是:
- 变量"pow"为函数名称(python中调用时所用的函数名称)
- math_pow函数指针,指向具体的函数实现
- 指定函数参数的格式
- 函数的帮助字符串
第四步:创建模块和初始化函数
定义好新的函数后,需要把方法放入某个模块下,这样才能使用python进行调用。
创建一个PyModuleDef类型的静态变量充当模块。
// ttps://github.com/python/cpython/blob/v3.7.2/Modules/mathmodule.c :2378
static struct PyModuleDef mathmodule = {
PyModuleDef_HEAD_INIT,
"math",
module_doc,
-1,
math_methods,
NULL,
NULL,
NULL,
NULL
};
/*
static struct PyModuleDef mathmodule = {
PyModuleDef_HEAD_INIT,
.m_name = "math",
.m_doc = module_doc,
.m_size = 0,
.m_methods = math_methods,
.m_slots = math_slots,
};
*/
有了模块的定义,还需要定义一个PyMODINIT_FUNC类型的函数,用来供Python解释器调用初始化模块
// https://github.com/python/cpython/blob/v3.7.2/Modules/mathmodule.c: 2309
PyMODINIT_FUNC
PyInit_math(void)
{
PyObject *m;
m = PyModule_Create(&mathmodule);
if (m == NULL)
goto finally;
// 省略一部分代码 ..
return m;
}
// 并针对 C++ 将函数声明为 extern "C"。
当 Python 程序首次导入 spam 模块时, PyInit_spam() 会被调用。 (有关嵌入 Python 的注释参见下文。) 它将调用 PyModule_Create(),该函数会返回一个模块对象,并基于在模块定义中找到的表将内置函数对象插入到新创建的模块中(该表是一个 PyMethodDef 结构体的数组)。 PyModule_Create() 返回一个指向它所创建的模块对象的指针。 它可能会因程度严重的特定错误而中止,或者在模块无法成功初始化时返回 NULL。 初始化函数必须返回模块对象给其调用者,这样它就可以被插入到 sys.modules 中。