编写你自己的 ufunc#
创建一个新的通用函数#
在阅读本文之前,通过阅读/浏览 扩展和嵌入Python解释器 第1节中的教程和 如何扩展NumPy ,熟悉Python的C扩展基础知识可能会有所帮助.
umath 模块是一个计算机生成的 C 模块,它创建了许多 ufuncs.它提供了许多如何创建通用函数的示例.创建一个利用 ufunc 机制的自己的 ufunc 也不难.假设你有一个函数,你希望它在输入上逐元素操作.通过创建一个新的 ufunc,你将获得一个处理
广播
N维循环
自动类型转换且内存使用最小化
可选输出数组
创建自己的 ufunc 并不难.所需要的只是一个针对每个你想要支持的数据类型的 1-d 循环.每个 1-d 循环必须有一个特定的签名,并且只能使用固定大小的数据类型的 ufuncs.下面给出了用于创建新的 ufunc 以处理内置数据类型的函数调用.使用不同的机制来注册用户定义数据类型的 ufuncs.
在接下来的几节中,我们给出了可以轻松修改以创建自己的ufuncs的示例代码.这些示例是逐步更完整或更复杂的logit函数版本,这是统计建模中常见的一个函数.Logit也很有趣,因为由于IEEE标准的魔力(特别是IEEE 754),下面创建的所有logit函数自动具有以下行为.
>>> logit(0)
-inf
>>> logit(1)
inf
>>> logit(2)
nan
>>> logit(-2)
nan
这很棒,因为函数编写者不必手动传播 infs 或 nans.
示例非ufunc扩展#
为了比较和让读者一般性了解,我们提供了一个不使用 numpy 的 logit
C 扩展的简单实现.
要做到这一点,我们需要两个文件.第一个是包含实际代码的C文件,第二个是用于创建模块的 setup.py
文件.
#define PY_SSIZE_T_CLEAN #include <Python.h> #include <math.h> /* * spammodule.c * This is the C code for a non-numpy Python extension to * define the logit function, where logit(p) = log(p/(1-p)). * This function will not work on numpy arrays automatically. * numpy.vectorize must be called in python to generate * a numpy-friendly function. * * Details explaining the Python-C API can be found under * 'Extending and Embedding' and 'Python/C API' at * docs.python.org . */ /* This declares the logit function */ static PyObject *spam_logit(PyObject *self, PyObject *args); /* * This tells Python what methods this module has. * See the Python-C API for more information. */ static PyMethodDef SpamMethods[] = { {"logit", spam_logit, METH_VARARGS, "compute logit"}, {NULL, NULL, 0, NULL} }; /* * This actually defines the logit function for * input args from Python. */ static PyObject *spam_logit(PyObject *self, PyObject *args) { double p; /* This parses the Python argument into a double */ if(!PyArg_ParseTuple(args, "d", &p)) { return NULL; } /* THE ACTUAL LOGIT FUNCTION */ p = p/(1-p); p = log(p); /*This builds the answer back into a python object */ return Py_BuildValue("d", p); } /* This initiates the module using the above definitions. */ static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "spam", NULL, -1, SpamMethods, NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit_spam(void) { PyObject *m; m = PyModule_Create(&moduledef); if (!m) { return NULL; } return m; }
要使用 setup.py 文件
,请将 setup.py
和 spammodule.c
放在同一个文件夹中.然后 python setup.py build
将构建模块以供导入,或者 python setup.py install
将模块安装到您的 site-packages 目录中.
''' setup.py file for spammodule.c Calling $python setup.py build_ext --inplace will build the extension library in the current file. Calling $python setup.py build will build a file that looks like ./build/lib*, where lib* is a file that begins with lib. The library will be in this file and end with a C library extension, such as .so Calling $python setup.py install will install the module in your site-packages file. See the setuptools section 'Building Extension Modules' at setuptools.pypa.io for more information. ''' from setuptools import setup, Extension import numpy as np module1 = Extension('spam', sources=['spammodule.c']) setup(name='spam', version='1.0', ext_modules=[module1])
一旦 spam 模块被导入到 python 中,你可以通过 spam.logit
调用 logit.注意,上面使用的函数不能直接应用于 numpy 数组.为此,我们必须在其上调用 numpy.vectorize
.例如,如果在一个包含 spam 库的文件中打开 python 解释器,或者 spam 已经被安装,可以执行以下命令:
>>> import numpy as np
>>> import spam
>>> spam.logit(0)
-inf
>>> spam.logit(1)
inf
>>> spam.logit(0.5)
0.0
>>> x = np.linspace(0,1,10)
>>> spam.logit(x)
TypeError: only length-1 arrays can be converted to Python scalars
>>> f = np.vectorize(spam.logit)
>>> f(x)
array([ -inf, -2.07944154, -1.25276297, -0.69314718, -0.22314355,
0.22314355, 0.69314718, 1.25276297, 2.07944154, inf])
结果的逻辑函数并不快! numpy.vectorize
只是循环遍历 spam.logit
.循环是在 C 层级完成的,但 numpy 数组不断地被解析和重建.这是昂贵的.当作者比较 numpy.vectorize(spam.logit)
与下面构造的逻辑 ufuncs 时,逻辑 ufuncs 几乎快了 4 倍.当然,根据函数的性质,速度提升可能更大或更小.
一个数据类型的示例 NumPy ufunc#
为了简单起见,我们为一个单一的数据类型 'f8'
双精度
提供了一个 ufunc.与上一节一样,我们首先给出 .c
文件,然后给出用于创建包含 ufunc 的模块的 setup.py
文件.
代码中对应ufunc实际计算的位置用``/* BEGIN main ufunc computation */和
/* END main ufunc computation */``标记.这些行之间的代码是必须更改的主要内容,以创建你自己的ufunc.
#define PY_SSIZE_T_CLEAN #include <Python.h> #include "numpy/ndarraytypes.h" #include "numpy/ufuncobject.h" #include "numpy/npy_3kcompat.h" #include <math.h> /* * single_type_logit.c * This is the C code for creating your own * NumPy ufunc for a logit function. * * In this code we only define the ufunc for * a single dtype. The computations that must * be replaced to create a ufunc for * a different function are marked with BEGIN * and END. * * Details explaining the Python-C API can be found under * 'Extending and Embedding' and 'Python/C API' at * docs.python.org . */ static PyMethodDef LogitMethods[] = { {NULL, NULL, 0, NULL} }; /* The loop definition must precede the PyMODINIT_FUNC. */ static void double_logit(char **args, const npy_intp *dimensions, const npy_intp *steps, void *data) { npy_intp i; npy_intp n = dimensions[0]; char *in = args[0], *out = args[1]; npy_intp in_step = steps[0], out_step = steps[1]; double tmp; for (i = 0; i < n; i++) { /* BEGIN main ufunc computation */ tmp = *(double *)in; tmp /= 1 - tmp; *((double *)out) = log(tmp); /* END main ufunc computation */ in += in_step; out += out_step; } } /* This a pointer to the above function */ PyUFuncGenericFunction funcs[1] = {&double_logit}; /* These are the input and return dtypes of logit.*/ static const char types[2] = {NPY_DOUBLE, NPY_DOUBLE}; static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "npufunc", NULL, -1, LogitMethods, NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit_npufunc(void) { PyObject *m, *logit, *d; import_array(); import_umath(); m = PyModule_Create(&moduledef); if (!m) { return NULL; } logit = PyUFunc_FromFuncAndData(funcs, NULL, types, 1, 1, 1, PyUFunc_None, "logit", "logit_docstring", 0); d = PyModule_GetDict(m); PyDict_SetItemString(d, "logit", logit); Py_DECREF(logit); return m; }
这是一个用于上述代码的 setup.py 文件
.如前所述,可以通过在命令提示符下调用 python setup.py build
来构建模块,或者通过 python setup.py install
安装到 site-packages 中.模块也可以通过 python setup.py build_ext --inplace
放置到本地文件夹中,例如 npufunc_directory
.
''' setup.py file for single_type_logit.c Note that since this is a numpy extension we add an include_dirs=[get_include()] so that the extension is built with numpy's C/C++ header files. Calling $python setup.py build_ext --inplace will build the extension library in the npufunc_directory. Calling $python setup.py build will build a file that looks like ./build/lib*, where lib* is a file that begins with lib. The library will be in this file and end with a C library extension, such as .so Calling $python setup.py install will install the module in your site-packages file. See the setuptools section 'Building Extension Modules' at setuptools.pypa.io for more information. ''' from setuptools import setup, Extension from numpy import get_include npufunc = Extension('npufunc', sources=['single_type_logit.c'], include_dirs=[get_include()]) setup(name='npufunc', version='1.0', ext_modules=[npufunc])
在上述内容安装完成后,可以按如下方式导入和使用.
>>> import numpy as np
>>> import npufunc
>>> npufunc.logit(0.5)
np.float64(0.0)
>>> a = np.linspace(0,1,5)
>>> npufunc.logit(a)
array([ -inf, -1.09861229, 0. , 1.09861229, inf])
带有多个数据类型的示例 NumPy ufunc#
我们最后给出了一个完整的 ufunc 示例,包含半浮点数、浮点数、双精度数和长双精度数的内循环.与前几节一样,我们首先给出 .c
文件,然后是相应的 setup.py
文件.
代码中对应于ufunc实际计算的位置用``/* BEGIN main ufunc computation */和
/* END main ufunc computation */``标记.这些行之间的代码是必须更改的主要内容,以创建你自己的ufunc.
#define PY_SSIZE_T_CLEAN #include <Python.h> #include "numpy/ndarraytypes.h" #include "numpy/ufuncobject.h" #include "numpy/halffloat.h" #include <math.h> /* * multi_type_logit.c * This is the C code for creating your own * NumPy ufunc for a logit function. * * Each function of the form type_logit defines the * logit function for a different numpy dtype. Each * of these functions must be modified when you * create your own ufunc. The computations that must * be replaced to create a ufunc for * a different function are marked with BEGIN * and END. * * Details explaining the Python-C API can be found under * 'Extending and Embedding' and 'Python/C API' at * docs.python.org . * */ static PyMethodDef LogitMethods[] = { {NULL, NULL, 0, NULL} }; /* The loop definitions must precede the PyMODINIT_FUNC. */ static void long_double_logit(char **args, const npy_intp *dimensions, const npy_intp *steps, void *data) { npy_intp i; npy_intp n = dimensions[0]; char *in = args[0], *out = args[1]; npy_intp in_step = steps[0], out_step = steps[1]; long double tmp; for (i = 0; i < n; i++) { /* BEGIN main ufunc computation */ tmp = *(long double *)in; tmp /= 1 - tmp; *((long double *)out) = logl(tmp); /* END main ufunc computation */ in += in_step; out += out_step; } } static void double_logit(char **args, const npy_intp *dimensions, const npy_intp *steps, void *data) { npy_intp i; npy_intp n = dimensions[0]; char *in = args[0], *out = args[1]; npy_intp in_step = steps[0], out_step = steps[1]; double tmp; for (i = 0; i < n; i++) { /* BEGIN main ufunc computation */ tmp = *(double *)in; tmp /= 1 - tmp; *((double *)out) = log(tmp); /* END main ufunc computation */ in += in_step; out += out_step; } } static void float_logit(char **args, const npy_intp *dimensions, const npy_intp *steps, void *data) { npy_intp i; npy_intp n = dimensions[0]; char *in = args[0], *out = args[1]; npy_intp in_step = steps[0], out_step = steps[1]; float tmp; for (i = 0; i < n; i++) { /* BEGIN main ufunc computation */ tmp = *(float *)in; tmp /= 1 - tmp; *((float *)out) = logf(tmp); /* END main ufunc computation */ in += in_step; out += out_step; } } static void half_float_logit(char **args, const npy_intp *dimensions, const npy_intp *steps, void *data) { npy_intp i; npy_intp n = dimensions[0]; char *in = args[0], *out = args[1]; npy_intp in_step = steps[0], out_step = steps[1]; float tmp; for (i = 0; i < n; i++) { /* BEGIN main ufunc computation */ tmp = npy_half_to_float(*(npy_half *)in); tmp /= 1 - tmp; tmp = logf(tmp); *((npy_half *)out) = npy_float_to_half(tmp); /* END main ufunc computation */ in += in_step; out += out_step; } } /*This gives pointers to the above functions*/ PyUFuncGenericFunction funcs[4] = {&half_float_logit, &float_logit, &double_logit, &long_double_logit}; static const char types[8] = {NPY_HALF, NPY_HALF, NPY_FLOAT, NPY_FLOAT, NPY_DOUBLE, NPY_DOUBLE, NPY_LONGDOUBLE, NPY_LONGDOUBLE}; static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "npufunc", NULL, -1, LogitMethods, NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit_npufunc(void) { PyObject *m, *logit, *d; import_array(); import_umath(); m = PyModule_Create(&moduledef); if (!m) { return NULL; } logit = PyUFunc_FromFuncAndData(funcs, NULL, types, 4, 1, 1, PyUFunc_None, "logit", "logit_docstring", 0); d = PyModule_GetDict(m); PyDict_SetItemString(d, "logit", logit); Py_DECREF(logit); return m; }
这是上述代码的 setup.py
文件.和之前一样,可以通过在命令提示符下调用 python setup.py build
来构建模块,或者通过 python setup.py install
安装到 site-packages 中.
''' setup.py file for multi_type_logit.c Note that since this is a numpy extension we add an include_dirs=[get_include()] so that the extension is built with numpy's C/C++ header files. Furthermore, we also have to include the npymath lib for half-float d-type. Calling $python setup.py build_ext --inplace will build the extension library in the current file. Calling $python setup.py build will build a file that looks like ./build/lib*, where lib* is a file that begins with lib. The library will be in this file and end with a C library extension, such as .so Calling $python setup.py install will install the module in your site-packages file. See the setuptools section 'Building Extension Modules' at setuptools.pypa.io for more information. ''' from setuptools import setup, Extension from numpy import get_include from os import path path_to_npymath = path.join(get_include(), '..', 'lib') npufunc = Extension('npufunc', sources=['multi_type_logit.c'], include_dirs=[get_include()], # Necessary for the half-float d-type. library_dirs=[path_to_npymath], libraries=["npymath"]) setup(name='npufunc', version='1.0', ext_modules=[npufunc])
在上述内容安装完成后,可以按如下方式导入和使用.
>>> import numpy as np
>>> import npufunc
>>> npufunc.logit(0.5)
np.float64(0.0)
>>> a = np.linspace(0,1,5)
>>> npufunc.logit(a)
array([ -inf, -1.09861229, 0. , 1.09861229, inf])
带有多个参数/返回值的 NumPy ufunc 示例#
我们的最终示例是一个带有多个参数的ufunc.这是对单个dtype数据进行logit ufunc代码的修改.我们计算 (A * B, logit(A * B))
.
我们只提供C代码,因为setup.py文件与`Example NumPy ufunc for one dtype`_中的``setup.py``文件完全相同,除了那一行
npufunc = Extension('npufunc', sources=['single_type_logit.c'], include_dirs=[get_include()])
被替换为
npufunc = Extension('npufunc', sources=['multi_arg_logit.c'], include_dirs=[get_include()])
下面给出了C文件.生成的ufunc接受两个参数 A
和 B
.它返回一个元组,其第一个元素是 A * B
,第二个元素是 logit(A * B)
.请注意,它自动支持广播,以及ufunc的所有其他属性.
#define PY_SSIZE_T_CLEAN #include <Python.h> #include "numpy/ndarraytypes.h" #include "numpy/ufuncobject.h" #include "numpy/halffloat.h" #include <math.h> /* * multi_arg_logit.c * This is the C code for creating your own * NumPy ufunc for a multiple argument, multiple * return value ufunc. The places where the * ufunc computation is carried out are marked * with comments. * * Details explaining the Python-C API can be found under * 'Extending and Embedding' and 'Python/C API' at * docs.python.org. */ static PyMethodDef LogitMethods[] = { {NULL, NULL, 0, NULL} }; /* The loop definition must precede the PyMODINIT_FUNC. */ static void double_logitprod(char **args, const npy_intp *dimensions, const npy_intp *steps, void *data) { npy_intp i; npy_intp n = dimensions[0]; char *in1 = args[0], *in2 = args[1]; char *out1 = args[2], *out2 = args[3]; npy_intp in1_step = steps[0], in2_step = steps[1]; npy_intp out1_step = steps[2], out2_step = steps[3]; double tmp; for (i = 0; i < n; i++) { /* BEGIN main ufunc computation */ tmp = *(double *)in1; tmp *= *(double *)in2; *((double *)out1) = tmp; *((double *)out2) = log(tmp / (1 - tmp)); /* END main ufunc computation */ in1 += in1_step; in2 += in2_step; out1 += out1_step; out2 += out2_step; } } /*This a pointer to the above function*/ PyUFuncGenericFunction funcs[1] = {&double_logitprod}; /* These are the input and return dtypes of logit.*/ static const char types[4] = {NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE}; static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "npufunc", NULL, -1, LogitMethods, NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit_npufunc(void) { PyObject *m, *logit, *d; import_array(); import_umath(); m = PyModule_Create(&moduledef); if (!m) { return NULL; } logit = PyUFunc_FromFuncAndData(funcs, NULL, types, 1, 2, 2, PyUFunc_None, "logit", "logit_docstring", 0); d = PyModule_GetDict(m); PyDict_SetItemString(d, "logit", logit); Py_DECREF(logit); return m; }
带有结构化数组dtype参数的NumPy ufunc示例#
这个例子展示了如何为一个结构化数组 dtype 创建一个 ufunc.对于这个例子,我们展示了一个微不足道的 ufunc,用于添加两个 dtype 为 'u8,u8,u8'
的数组.这个过程与其他例子有点不同,因为调用 PyUFunc_FromFuncAndData
并不能完全注册自定义 dtype 和结构化数组 dtype 的 ufuncs.我们还需要调用 PyUFunc_RegisterLoopForDescr
来完成设置 ufunc.
我们只提供C代码,因为 setup.py
文件与 Example NumPy ufunc for one dtype 中的 setup.py
文件完全相同,除了那一行
npufunc = Extension('npufunc', sources=['single_type_logit.c'], include_dirs=[get_include()])
被替换为
npufunc = Extension('npufunc', sources=['add_triplet.c'], include_dirs=[get_include()])
下面给出了C文件.
#define PY_SSIZE_T_CLEAN #include <Python.h> #include "numpy/ndarraytypes.h" #include "numpy/ufuncobject.h" #include "numpy/npy_3kcompat.h" #include <math.h> /* * add_triplet.c * This is the C code for creating your own * NumPy ufunc for a structured array dtype. * * Details explaining the Python-C API can be found under * 'Extending and Embedding' and 'Python/C API' at * docs.python.org. */ static PyMethodDef StructUfuncTestMethods[] = { {NULL, NULL, 0, NULL} }; /* The loop definition must precede the PyMODINIT_FUNC. */ static void add_uint64_triplet(char **args, const npy_intp *dimensions, const npy_intp *steps, void *data) { npy_intp i; npy_intp is1 = steps[0]; npy_intp is2 = steps[1]; npy_intp os = steps[2]; npy_intp n = dimensions[0]; uint64_t *x, *y, *z; char *i1 = args[0]; char *i2 = args[1]; char *op = args[2]; for (i = 0; i < n; i++) { x = (uint64_t *)i1; y = (uint64_t *)i2; z = (uint64_t *)op; z[0] = x[0] + y[0]; z[1] = x[1] + y[1]; z[2] = x[2] + y[2]; i1 += is1; i2 += is2; op += os; } } /* This a pointer to the above function */ PyUFuncGenericFunction funcs[1] = {&add_uint64_triplet}; /* These are the input and return dtypes of add_uint64_triplet. */ static const char types[3] = {NPY_UINT64, NPY_UINT64, NPY_UINT64}; static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "struct_ufunc_test", NULL, -1, StructUfuncTestMethods, NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit_npufunc(void) { PyObject *m, *add_triplet, *d; PyObject *dtype_dict; PyArray_Descr *dtype; PyArray_Descr *dtypes[3]; import_array(); import_umath(); m = PyModule_Create(&moduledef); if (m == NULL) { return NULL; } /* Create a new ufunc object */ add_triplet = PyUFunc_FromFuncAndData(NULL, NULL, NULL, 0, 2, 1, PyUFunc_None, "add_triplet", "add_triplet_docstring", 0); dtype_dict = Py_BuildValue("[(s, s), (s, s), (s, s)]", "f0", "u8", "f1", "u8", "f2", "u8"); PyArray_DescrConverter(dtype_dict, &dtype); Py_DECREF(dtype_dict); dtypes[0] = dtype; dtypes[1] = dtype; dtypes[2] = dtype; /* Register ufunc for structured dtype */ PyUFunc_RegisterLoopForDescr(add_triplet, dtype, &add_uint64_triplet, dtypes, NULL); d = PyModule_GetDict(m); PyDict_SetItemString(d, "add_triplet", add_triplet); Py_DECREF(add_triplet); return m; }
返回的 ufunc 对象是一个可调用的 Python 对象.它应该放在一个(模块)字典中,使用与 ufunc-creation 例程的 name 参数相同的名称.以下示例改编自 umath 模块
static PyUFuncGenericFunction atan2_functions[] = { PyUFunc_ff_f, PyUFunc_dd_d, PyUFunc_gg_g, PyUFunc_OO_O_method}; static void *atan2_data[] = { (void *)atan2f, (void *)atan2, (void *)atan2l, (void *)"arctan2"}; static const char atan2_signatures[] = { NPY_FLOAT, NPY_FLOAT, NPY_FLOAT, NPY_DOUBLE, NPY_DOUBLE, NPY_DOUBLE, NPY_LONGDOUBLE, NPY_LONGDOUBLE, NPY_LONGDOUBLE NPY_OBJECT, NPY_OBJECT, NPY_OBJECT}; ... /* in the module initialization code */ PyObject *f, *dict, *module; ... dict = PyModule_GetDict(module); ... f = PyUFunc_FromFuncAndData(atan2_functions, atan2_data, atan2_signatures, 4, 2, 1, PyUFunc_None, "arctan2", "a safe and correct arctan(x1/x2)", 0); PyDict_SetItemString(dict, "arctan2", f); Py_DECREF(f); ...