標籤:ast 解決方案 傳遞 lin typeerror swig 被記憶體回收 adl highlight
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 encoding53 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 string53 70 69 63 79 20 4a 61 6c 61 70 65 c3 b1 6f>>> sys.getsizeof(s) # Notice increased size103>>>
如果你在乎這個記憶體的損耗,你最好重寫你的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/)國內第一家人工智慧門戶
Python Cookbook(第3版)中文版:15.13 傳遞NULL結尾的字串給C函數庫