Morales的Python學習筆記#C語言擴展 11

把C字元串轉換到Python中

假設我們想把C的字元串轉換為Python位元組流或者字元串對象

對於以char*, int, 形式表示的C字元串,我們必須決定是否要把他們以原始位元組串或者Unicode字元串的形式來表示。位元組對象可以通過Py_BuildValue()來構建:

char *s; /* Pointer to C string data */
int len; /* Length of data */

/* Make a bytes object */
PyObject *obj = Py_BuildValue("y#", s, len);

如果你想構建一個Unicode字元串,而且知道s指向的數據都是以UTF-8來進行編碼的,則可以使用如下的代碼來完成:

PyObject *obj = Py_BuildValue("s#", s, len);

如果s使用其他編碼方式,那麼可以像下面使用PyUnicode_Decode()來構建一個字元串:

PyObject *obj = PyUnicode_Decode(s, len, "encoding", "errors");

/* Examples /*
obj = PyUnicode_Decode(s, len, "latin-1", "strict");
obj = PyUnicode_Decode(s, len, "ascii", "ignore");

如果你剛好有一個以wchar_t *, len表示的寬字元串,這裡就有一些選擇。首先,我們可以像這樣使用Py_BuildValue():

wchar_t *w; /* Wide character string */
int len; /* Length */

PyObject *obj = Py_BuildValue("u#", w, len);

另外,你還可以使用PyUnicode_FromWideChar():

PyObject *obj = PyUnicode_FromWideChar(w, len);

將C中的字元串轉換為Python字元串遵循和I/O同樣的原則。 也就是說,來自C中的數據必須根據一些解碼器被顯式的解碼為一個字元串。 通常編碼格式包括ASCII、Latin-1和UTF-8. 如果你並不確定編碼方式或者數據是二進位的,你最好將字元串編碼成位元組。 當構造一個對象的時候,Python通常會複製你提供的字元串數據。 如果有必要的話,你需要在後面去釋放C字元串。 同時,為了讓程序更加健壯,你應該同時使用一個指針和一個大小值, 而不是依賴NULL結尾數據來創建字元串。

不確定編碼格式的C字元串

你要在C和Python直接來迴轉換字元串,但是C中的編碼格式並不確定。 例如,可能C中的數據期望是UTF-8,但是並沒有強制它必須是。 你想編寫代碼來以一種優雅的方式處理這些不合格數據,這樣就不會讓Python奔潰或者破壞進程中的字元串數據。

下面是一些C的數據和一個函數來演示這個問題:

/* Some dubious string data (malformed UTF-8) */
const char *sdata = "Spicy Jalapexc3xb1oxae";
int slen = 16;

/* Output character data */
void print_chars(char *s, int len) {
int n = 0;
while (n < len) {
printf("%2x ", (unsigned char) s[n]);
n++;
}
printf("
");
}

在這個代碼中,字元串sdata包含了UTF-8和不合格數據。 不過,如果用戶在C中調用print_chars(sdata,slen),它缺能正常工作。 現在假設你想將sdata的內容轉換為一個Python字元串。 進一步假設你在後面還想通過一個擴展將那個字元串傳個print_chars()函數。 下面是一種用來保護原始數據的方法,就算它編碼有問題。

/* Return the C string back to Python */
static PyObject *py_retstr(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(args, "")) {
return NULL;
}
return PyUnicode_Decode(sdata, slen, "utf-8", "surrogateescape");
}

/* Wrapper for the print_chars() function */
static PyObject *py_print_chars(PyObject *self, PyObject *args) {
PyObject *obj, *bytes;
char *s = 0;
Py_ssize_t len;

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

if ((bytes = PyUnicode_AsEncodedString(obj,"utf-8","surrogateescape"))
== NULL) {
return NULL;
}
PyBytes_AsStringAndSize(bytes, &s, &len);
print_chars(s, len);
Py_DECREF(bytes);
Py_RETURN_NONE;
}

本節展示了在擴展模塊中處理字元串時會配到的一個棘手又很惱火的問題。 也就是說,在擴展中的C字元串可能不會嚴格遵循Python所期望的Unicode編碼/解碼規則。 因此,很可能一些不合格C數據傳遞到Python中去。 一個很好的例子就是涉及到底層系統調用比如文件名這樣的字元串。 例如,如果一個系統調用返回給解釋器一個損壞的字元串,不能被正確解碼的時候會怎樣呢?

一般來講,可以通過制定一些錯誤策略比如嚴格、忽略、替代或其他類似的來處理Unicode錯誤。 不過,這些策略的一個缺點是它們永久性破壞了原始字元串的內容。 例如,如果例子中的不合格數據使用這些策略之一解碼,你會得到下面這樣的結果:

>>> raw = bSpicy Jalapexc3xb1oxae
>>> raw.decode(utf-8,ignore)
Spicy Jalape?o
>>> raw.decode(utf-8,replace)
Spicy Jalape?o?
>>>

surrogateescape錯誤處理策略會將所有不可解碼位元組轉化為一個代理對的低位位元組(udcXX中XX是原始位元組值)。 例如:

>>> raw.decode(utf-8,surrogateescape)
Spicy Jalape?oudcae
>>>

作為一般準則,最好避免代理編碼——如果你正確的使用了編碼,那麼你的代碼就值得信賴。 不過,有時候確實會出現你並不能控制數據編碼並且你又不能忽略或替換壞數據,因為其他函數可能會用到它。 那麼就可以使用本節的技術了。

最後一點要注意的是,Python中許多面向系統的函數,特別是和文件名、環境變數和命令行參數相關的 都會使用代理編碼。例如,如果你使用像 os.listdir() 這樣的函數, 傳入一個包含了不可解碼文件名的目錄的話,它會返回一個代理轉換後的字元串。 參考5.15的相關章節。

PEP 383 -- Non-decodable Bytes in System Character Interfaces?

www.python.org圖標

中有更多關於本機提到的以及和surrogateescape錯誤處理相關的信息。

參考書目

《Python CookBook》作者:【美】 David Beazley, Brian K. Jones

Python 3.0.0文檔:

Python Cookbook 3rd Edition Documentation?

python3-cookbook.readthedocs.io


推薦閱讀:

如何加密/混亂C語言源代碼
C語言文件操作
設計模式的C語言應用-命令模式
C語言基礎:關鍵字元號與變數
C語言文件操作之——文件的讀寫(詳解,附學習資料)

TAG:C(編程語言) | Python入門 |