Weibw's World Weibw's World
首页
  • HTML
  • Python

    • Python基础知识
    • Python CookBook第三版
    • Flask
  • MySQL

    • MySQL基础知识
    • MySQL调优
    • MySQL面试题
算法
  • FineReport
  • Kettle
  • Git
  • 微信公众号文章
  • 优秀博客文章
  • 其他
收藏夹
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Weibw

一个没有梦想的咸鱼
首页
  • HTML
  • Python

    • Python基础知识
    • Python CookBook第三版
    • Flask
  • MySQL

    • MySQL基础知识
    • MySQL调优
    • MySQL面试题
算法
  • FineReport
  • Kettle
  • Git
  • 微信公众号文章
  • 优秀博客文章
  • 其他
收藏夹
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 《Flask》

  • 《Python Cookbook》第三版

    • 第一章:数据结构与算法

    • 第二章:字符串和文本

    • 第三章:数字日期和时间

    • 第四章:迭代器与生成器

    • 第五章:文件与IO

    • 第六章:数据编码和处理

    • 第七章:函数

    • 第八章:类与对象

    • 第九章:元编程

    • 第十章:模块与包

    • 第十一章:网络与Web编程

    • 第十二章:并发编程

    • 第十三章:脚本编程与系统管理

    • 第十四章:测试、调试和异常

    • 第十五章:C语言扩展

      • 使用ctypes访问C代码
      • 简单的C扩展模块
      • 编写扩展函数操作数组
        • 问题
        • 解决方案
        • 讨论
      • 在C扩展模块中操作隐形指针
      • 从扩展模块中定义和导出C的API
      • 从C语言中调用Python代码
      • 从C扩展中释放全局锁
      • C和Python中的线程混用
      • 用SWIG包装C代码
      • 用Cython包装C代码
      • 用Cython写高性能的数组操作
      • 将函数指针转换为可调用对象
      • 传递NULL结尾的字符串给C函数库
      • 传递Unicode字符串给C函数库
      • C字符串转换为Python字符串
      • 不确定编码格式的C字符串
      • 传递文件名给C扩展
      • 传递已打开的文件给C扩展
      • 从C语言中读取类文件对象
      • 处理C语言中的可迭代对象
      • 诊断分段错误
  • Python基础

  • Python
  • 《Python Cookbook》第三版
  • 第十五章:C语言扩展
weibw
2022-01-18

编写扩展函数操作数组

# 问题

你想编写一个C扩展函数来操作数组,可能是被array模块或类似Numpy库所创建。 不过,你想让你的函数更加通用,而不是针对某个特定的库所生成的数组。

# 解决方案

为了能让接受和处理数组具有可移植性,你需要使用到 Buffer Protocol . 下面是一个手写的C扩展函数例子, 用来接受数组数据并调用本章开篇部分的 avg(double *buf, int len) 函数:

/* Call double avg(double *, int) */
static PyObject *py_avg(PyObject *self, PyObject *args) {
  PyObject *bufobj;
  Py_buffer view;
  double result;
  /* Get the passed Python object */
  if (!PyArg_ParseTuple(args, "O", &bufobj)) {
    return NULL;
  }

  /* Attempt to extract buffer information from it */

  if (PyObject_GetBuffer(bufobj, &view,
      PyBUF_ANY_CONTIGUOUS | PyBUF_FORMAT) == -1) {
    return NULL;
  }

  if (view.ndim != 1) {
    PyErr_SetString(PyExc_TypeError, "Expected a 1-dimensional array");
    PyBuffer_Release(&view);
    return NULL;
  }

  /* Check the type of items in the array */
  if (strcmp(view.format,"d") != 0) {
    PyErr_SetString(PyExc_TypeError, "Expected an array of doubles");
    PyBuffer_Release(&view);
    return NULL;
  }

  /* Pass the raw buffer and size to the C function */
  result = avg(view.buf, view.shape[0]);

  /* Indicate we're done working with the buffer */
  PyBuffer_Release(&view);
  return Py_BuildValue("d", result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

下面我们演示下这个扩展函数是如何工作的:

>>> import array
>>> avg(array.array('d',[1,2,3]))
2.0
>>> import numpy
>>> avg(numpy.array([1.0,2.0,3.0]))
2.0
>>> avg([1,2,3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'list' does not support the buffer interface
>>> avg(b'Hello')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Expected an array of doubles
>>> a = numpy.array([[1.,2.,3.],[4.,5.,6.]])
>>> avg(a[:,2])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: ndarray is not contiguous
>>> sample.avg(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Expected a 1-dimensional array
>>> sample.avg(a[0])

2.0
>>>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 讨论

将一个数组对象传给C函数可能是一个扩展函数做的最常见的事。 很多Python应用程序,从图像处理到科学计算,都是基于高性能的数组处理。 通过编写能接受并操作数组的代码,你可以编写很好的兼容这些应用程序的自定义代码, 而不是只能兼容你自己的代码。

代码的关键点在于 PyBuffer_GetBuffer() 函数。 给定一个任意的Python对象,它会试着去获取底层内存信息,它简单的抛出一个异常并返回-1. 传给 PyBuffer_GetBuffer() 的特殊标志给出了所需的内存缓冲类型。 例如,PyBUF_ANY_CONTIGUOUS 表示是一个连续的内存区域。

对于数组、字节字符串和其他类似对象而言,一个 Py_buffer 结构体包含了所有底层内存的信息。 它包含一个指向内存地址、大小、元素大小、格式和其他细节的指针。下面是这个结构体的定义:

typedef struct bufferinfo {
    void *buf;              /* Pointer to buffer memory */
    PyObject *obj;          /* Python object that is the owner */
    Py_ssize_t len;         /* Total size in bytes */
    Py_ssize_t itemsize;    /* Size in bytes of a single item */
    int readonly;           /* Read-only access flag */
    int ndim;               /* Number of dimensions */
    char *format;           /* struct code of a single item */
    Py_ssize_t *shape;      /* Array containing dimensions */
    Py_ssize_t *strides;    /* Array containing strides */
    Py_ssize_t *suboffsets; /* Array containing suboffsets */
} Py_buffer;
1
2
3
4
5
6
7
8
9
10
11
12

本节中,我们只关注接受一个双精度浮点数数组作为参数。 要检查元素是否是一个双精度浮点数,只需验证 format 属性是不是字符串”d”. 这个也是 struct 模块用来编码二进制数据的。 通常来讲,format 可以是任何兼容 struct 模块的格式化字符串, 并且如果数组包含了C结构的话它可以包含多个值。 一旦我们已经确定了底层的缓存区信息,那只需要简单的将它传给C函数,然后会被当做是一个普通的C数组了。 实际上,我们不必担心是怎样的数组类型或者它是被什么库创建出来的。 这也是为什么这个函数能兼容 array 模块也能兼容 numpy 模块中的数组了。

在返回最终结果之前,底层的缓冲区视图必须使用 PyBuffer_Release() 释放掉。 之所以要这一步是为了能正确的管理对象的引用计数。

同样,本节也仅仅只是演示了接受数组的一个小的代码片段。 如果你真的要处理数组,你可能会碰到多维数据、大数据、不同的数据类型等等问题, 那么就得去学更高级的东西了。你需要参考官方文档来获取更多详细的细节。

如果你需要编写涉及到数组处理的多个扩展,那么通过Cython来实现会更容易下。参考15.11节。

编辑 (opens new window)
上次更新: 2023/10/13, 17:39:25
简单的C扩展模块
在C扩展模块中操作隐形指针

← 简单的C扩展模块 在C扩展模块中操作隐形指针→

最近更新
01
牛客网非技术快速入门SQL练习题
03-08
02
其他日常SQL题
03-07
03
用户与权限管理
03-05
更多文章>
Theme by Vdoing | Copyright © 2021-2023 | Weibw | 辽ICP备18015889号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式