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语言中读取类文件对象

# 问题

你要写C扩展来读取来自任何Python类文件对象中的数据(比如普通文件、StringIO对象等)。

# 解决方案

要读取一个类文件对象的数据,你需要重复调用 read() 方法,然后正确的解码获得的数据。

下面是一个C扩展函数例子,仅仅只是读取一个类文件对象中的所有数据并将其输出到标准输出:

#define CHUNK_SIZE 8192

/* Consume a "file-like" object and write bytes to stdout */
static PyObject *py_consume_file(PyObject *self, PyObject *args) {
  PyObject *obj;
  PyObject *read_meth;
  PyObject *result = NULL;
  PyObject *read_args;

  if (!PyArg_ParseTuple(args,"O", &obj)) {
    return NULL;
  }

  /* Get the read method of the passed object */
  if ((read_meth = PyObject_GetAttrString(obj, "read")) == NULL) {
    return NULL;
  }

  /* Build the argument list to read() */
  read_args = Py_BuildValue("(i)", CHUNK_SIZE);
  while (1) {
    PyObject *data;
    PyObject *enc_data;
    char *buf;
    Py_ssize_t len;

    /* Call read() */
    if ((data = PyObject_Call(read_meth, read_args, NULL)) == NULL) {
      goto final;
    }

    /* Check for EOF */
    if (PySequence_Length(data) == 0) {
      Py_DECREF(data);
      break;
    }

    /* Encode Unicode as Bytes for C */
    if ((enc_data=PyUnicode_AsEncodedString(data,"utf-8","strict"))==NULL) {
      Py_DECREF(data);
      goto final;
    }

    /* Extract underlying buffer data */
    PyBytes_AsStringAndSize(enc_data, &buf, &len);

    /* Write to stdout (replace with something more useful) */
    write(1, buf, len);

    /* Cleanup */
    Py_DECREF(enc_data);
    Py_DECREF(data);
  }
  result = Py_BuildValue("");

 final:
  /* Cleanup */
  Py_DECREF(read_meth);
  Py_DECREF(read_args);
  return 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

要测试这个代码,先构造一个类文件对象比如一个StringIO实例,然后传递进来:

>>> import io
>>> f = io.StringIO('Hello\nWorld\n')
>>> import sample
>>> sample.consume_file(f)
Hello
World
>>>
1
2
3
4
5
6
7

# 讨论

和普通系统文件不同的是,一个类文件对象并不需要使用低级文件描述符来构建。 因此,你不能使用普通的C库函数来访问它。 你需要使用Python的C API来像普通文件类似的那样操作类文件对象。

在我们的解决方案中,read() 方法从被传递的对象中提取出来。 一个参数列表被构建然后不断的被传给 PyObject_Call() 来调用这个方法。 要检查文件末尾(EOF),使用了 PySequence_Length() 来查看是否返回对象长度为0.

对于所有的I/O操作,你需要关注底层的编码格式,还有字节和Unicode之前的区别。 本节演示了如何以文本模式读取一个文件并将结果文本解码为一个字节编码,这样在C中就可以使用它了。 如果你想以二进制模式读取文件,只需要修改一点点即可,例如:

...
/* Call read() */
if ((data = PyObject_Call(read_meth, read_args, NULL)) == NULL) {
  goto final;
}

/* Check for EOF */
if (PySequence_Length(data) == 0) {
  Py_DECREF(data);
  break;
}
if (!PyBytes_Check(data)) {
  Py_DECREF(data);
  PyErr_SetString(PyExc_IOError, "File must be in binary mode");
  goto final;
}

/* Extract underlying buffer data */
PyBytes_AsStringAndSize(data, &buf, &len);
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

本节最难的地方在于如何进行正确的内存管理。 当处理 PyObject * 变量的时候,需要注意管理引用计数以及在不需要的变量的时候清理它们的值。 对 Py_DECREF() 的调用就是来做这个的。

本节代码以一种通用方式编写,因此他也能适用于其他的文件操作,比如写文件。 例如,要写数据,只需要获取类文件对象的 write() 方法,将数据转换为合适的Python对象 (字节或Unicode),然后调用该方法将输入写入到文件。

最后,尽管类文件对象通常还提供其他方法(比如readline(), read_info()), 我们最好只使用基本的 read() 和 write() 方法。 在写C扩展的时候,能简单就尽量简单。

编辑 (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号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式