Python Cookbook(第3版)中文版:15.13 传递NULL结尾的字符串给C函数库

15.13 传递NULL结尾的字符串给C函数库?

问题?

你要写一个扩展模块,需要传递一个NULL结尾的字符串给C函数库。
不过,你不是很确定怎样使用Python的Unicode字符串去实现它。

解决方案?

许多C函数库包含一些操作NULL结尾的字符串,被声明类型为 char * .
考虑如下的C函数,我们用来做演示和测试用的:

void print_chars(char *s) {
    while (*s) {
        printf("%2x ", (unsigned char) *s);

        s++;
    }
    printf("\n");
}

此函数会打印被传进来字符串的每个字符的十六进制表示,这样的话可以很容易的进行调试了。例如:

print_chars("Hello");   // Outputs: 48 65 6c 6c 6f

对于在Python中调用这样的C函数,你有几种选择。
首先,你可以通过调用 PyArg_ParseTuple() 并指定”y“转换码来限制它只能操作字节,如下:

static PyObject *py_print_chars(PyObject *self, PyObject *args) {
  char *s;

  if (!PyArg_ParseTuple(args, "y", &s)) {
    return NULL;
  }
  print_chars(s);
  Py_RETURN_NONE;
}

结果函数的使用方法如下。仔细观察嵌入了NULL字节的字符串以及Unicode支持是怎样被拒绝的:

>>> print_chars(b‘Hello World‘)
48 65 6c 6c 6f 20 57 6f 72 6c 64
>>> print_chars(b‘Hello\x00World‘)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: must be bytes without null bytes, not bytes
>>> print_chars(‘Hello World‘)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ‘str‘ does not support the buffer interface
>>>

如果你想传递Unicode字符串,在 PyArg_ParseTuple() 中使用”s“格式码,如下:

static PyObject *py_print_chars(PyObject *self, PyObject *args) {
  char *s;

  if (!PyArg_ParseTuple(args, "s", &s)) {
    return NULL;
  }
  print_chars(s);
  Py_RETURN_NONE;
}

当被使用的时候,它会自动将所有字符串转换为以NULL结尾的UTF-8编码。例如:

>>> print_chars(‘Hello World‘)
48 65 6c 6c 6f 20 57 6f 72 6c 64
>>> print_chars(‘Spicy Jalape\u00f1o‘)  # Note: UTF-8 encoding
53 70 69 63 79 20 4a 61 6c 61 70 65 c3 b1 6f
>>> print_chars(‘Hello\x00World‘)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: must be str without null characters, not str
>>> print_chars(b‘Hello World‘)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: must be str, not bytes
>>>

如果因为某些原因,你要直接使用 PyObject * 而不能使用 PyArg_ParseTuple() ,
下面的例子向你展示了怎样从字节和字符串对象中检查和提取一个合适的 char * 引用:

/* Some Python Object (obtained somehow) */
PyObject *obj;

/* Conversion from bytes */
{
   char *s;
   s = PyBytes_AsString(o);
   if (!s) {
      return NULL;   /* TypeError already raised */
   }
   print_chars(s);
}

/* Conversion to UTF-8 bytes from a string */
{
   PyObject *bytes;
   char *s;
   if (!PyUnicode_Check(obj)) {
       PyErr_SetString(PyExc_TypeError, "Expected string");
       return NULL;
   }
   bytes = PyUnicode_AsUTF8String(obj);
   s = PyBytes_AsString(bytes);
   print_chars(s);
   Py_DECREF(bytes);
}

前面两种转换都可以确保是NULL结尾的数据,
但是它们并不检查字符串中间是否嵌入了NULL字节。
因此,如果这个很重要的话,那你需要自己去做检查了。

讨论?

如果可能的话,你应该避免去写一些依赖于NULL结尾的字符串,因为Python并没有这个需要。
最好结合使用一个指针和长度值来处理字符串。
不过,有时候你必须去处理C语言遗留代码时就没得选择了。

尽管很容易使用,但是很容易忽视的一个问题是在 PyArg_ParseTuple()
中使用“s”格式化码会有内存损耗。
但你需要使用这种转换的时候,一个UTF-8字符串被创建并永久附加在原始字符串对象上面。
如果原始字符串包含非ASCII字符的话,就会导致字符串的尺寸增到一直到被垃圾回收。例如:

>>> import sys
>>> s = ‘Spicy Jalape\u00f1o‘
>>> sys.getsizeof(s)
87
>>> print_chars(s)     # Passing string
53 70 69 63 79 20 4a 61 6c 61 70 65 c3 b1 6f
>>> sys.getsizeof(s)   # Notice increased size
103
>>>

如果你在乎这个内存的损耗,你最好重写你的C扩展代码,让它使用 PyUnicode_AsUTF8String() 函数。如下:

static PyObject *py_print_chars(PyObject *self, PyObject *args) {
  PyObject *o, *bytes;
  char *s;

  if (!PyArg_ParseTuple(args, "U", &o)) {
    return NULL;
  }
  bytes = PyUnicode_AsUTF8String(o);
  s = PyBytes_AsString(bytes);
  print_chars(s);
  Py_DECREF(bytes);
  Py_RETURN_NONE;
}

通过这个修改,一个UTF-8编码的字符串根据需要被创建,然后在使用过后被丢弃。下面是修订后的效果:

>>> import sys
>>> s = ‘Spicy Jalape\u00f1o‘
>>> sys.getsizeof(s)
87
>>> print_chars(s)
53 70 69 63 79 20 4a 61 6c 61 70 65 c3 b1 6f
>>> sys.getsizeof(s)
87
>>>

如果你试着传递NULL结尾字符串给ctypes包装过的函数,
要注意的是ctypes只能允许传递字节,并且它不会检查中间嵌入的NULL字节。例如:

>>> import ctypes
>>> lib = ctypes.cdll.LoadLibrary("./libsample.so")
>>> print_chars = lib.print_chars
>>> print_chars.argtypes = (ctypes.c_char_p,)
>>> print_chars(b‘Hello World‘)
48 65 6c 6c 6f 20 57 6f 72 6c 64
>>> print_chars(b‘Hello\x00World‘)
48 65 6c 6c 6f
>>> print_chars(‘Hello World‘)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 1: <class ‘TypeError‘>: wrong type
>>>

如果你想传递字符串而不是字节,你需要先执行手动的UTF-8编码。例如:

>>> print_chars(‘Hello World‘.encode(‘utf-8‘))
48 65 6c 6c 6f 20 57 6f 72 6c 64
>>>

对于其他扩展工具(比如Swig、Cython),
在你使用它们传递字符串给C代码时要先好好学习相应的东西了。

艾伯特(http://www.aibbt.com/)国内第一家人工智能门户

原文地址:https://www.cnblogs.com/5rjscn/p/8542831.html

时间: 2024-08-27 05:42:28

Python Cookbook(第3版)中文版:15.13 传递NULL结尾的字符串给C函数库的相关文章

Python Cookbook(第3版)中文版:15.12 将函数指针转换为可调用对象

15.12 将函数指针转换为可调用对象? 问题? 你已经获得了一个被编译函数的内存地址,想将它转换成一个Python可调用对象,这样的话你就可以将它作为一个扩展函数使用了. 解决方案? ctypes 模块可被用来创建包装任意内存地址的Python可调用对象.下面的例子演示了怎样获取C函数的原始.底层地址,以及如何将其转换为一个可调用对象: >>> import ctypes >>> lib = ctypes.cdll.LoadLibrary(None) >>

Python Cookbook(第3版)中文版:15.1 使用ctypes访问C代码

15.1 使用ctypes访问C代码? 问题? 你有一些C函数已经被编译到共享库或DLL中.你希望可以使用纯Python代码调用这些函数,而不用编写额外的C代码或使用第三方扩展工具. 解决方案? 对于需要调用C代码的一些小的问题,通常使用Python标准库中的 ctypes 模块就足够了.要使用 ctypes ,你首先要确保你要访问的C代码已经被编译到和Python解释器兼容(同样的架构.字大小.编译器等)的某个共享库中了.为了进行本节的演示,假设你有一个共享库名字叫 libsample.so 

Python Cookbook(第3版) 中文版 pdf完整版高清下载

Python Cookbook(第3版)中文版介绍了Python应用在各个领域中的一些使用技巧和方法,其主题涵盖了数据结构和算法,字符串和文本,数字.日期和时间,迭代器和生成器,文件和I/O,数据编码与处理,函数,类与对象,元编程,模块和包,网络和Web编程,并发,实用脚本和系统管理,测试.调试以及异常,C语言扩展等. 本书覆盖了Python应用中的很多常见问题,并提出了通用的解决方案.书中包含了大量实用的编程技巧和示例代码,并在Python 3.3环境下进行了测试,可以很方便地应用到实际项目中

Python Cookbook(第3版)中文版:14.11 输出警告信息

14.11 输出警告信息? 问题? 你希望自己的程序能生成警告信息(比如废弃特性或使用问题). 解决方案? 要输出一个警告消息,可使用 warning.warn() 函数.例如: import warnings def func(x, y, logfile=None, debug=False): if logfile is not None: warnings.warn('logfile argument deprecated', DeprecationWarning) ... warn() 的

Python Cookbook(第3版)中文版:15.11 用Cython写高性能的数组操作

15.11 用Cython写高性能的数组操作? 问题? 你要写高性能的操作来自NumPy之类的数组计算函数.你已经知道了Cython这样的工具会让它变得简单,但是并不确定该怎样去做. 解决方案? 作为一个例子,下面的代码演示了一个Cython函数,用来修整一个简单的一维双精度浮点数数组中元素的值. # sample.pyx (Cython) cimport cython @cython.boundscheck(False) @cython.wraparound(False) cpdef clip

Python Cookbook(第3版)中文版:14.12 调试基本的程序崩溃错误

14.12 调试基本的程序崩溃错误? 问题? 你的程序崩溃后该怎样去调试它? 解决方案? 如果你的程序因为某个异常而崩溃,运行 python3 -i someprogram.py 可执行简单的调试.-i 选项可让程序结束后打开一个交互式shell.然后你就能查看环境,例如,假设你有下面的代码: # sample.py def func(n): return n + 10 func('Hello') 运行 python3 -i sample.py 会有类似如下的输出: bash % python3

python书籍推荐:Python Cookbook第三版中文

所属网站分类: 资源下载 > python电子书 作者:熊猫烧香 链接:http://www.pythonheidong.com/blog/article/44/ 来源:python黑洞网 内容简介 <Python Cookbook(第3版)中文版>介绍了Python应用在各个领域中的一些使用技巧和方法,其主题涵盖了数据结构和算法,字符串和文本,数字.日期和时间,迭代器和生成器,文件和I/O,数据编码与处理,函数,类与对象,元编程,模块和包,网络和Web编程,并发,实用脚本和系统管理,测

python cookbook第三版学习笔记六:迭代器与生成器

假如我们有一个列表 items=[1,2,3].我们要遍历这个列表我们会用下面的方式 For i in items:   Print i 首先介绍几个概念:容器,可迭代对象,迭代器 容器是一种存储数据的数据结构,容器将所有数据保存在内存中,典型的容器有列表,集合,字典,字符数组等.如items就是一个列表容器.   可迭代对象:这个对象是否可迭代.如items也是一个可迭代对象.简单来说如果可以用for循环的对象都称为可迭代对象.如果要判断是否是一个可迭代的对象.可以用print isinsta

python cookbook第三版学习笔记五:datetime

Python中表示时间的模块是datetime,引入下面的模块 from datetime import datetime,timedelta print datetime.today()  #打印出当前的时间 E:\python2.7.11\python.exe E:/py_prj/python_cookbook.py 2017-04-26 21:58:05.663000 我们还可以对时间进行加减操作.这里要用到timedelta模块 这个模块有5个重要参数分别是days,minutes,se