Передача строк с завершающим NULL в библиотеки C

Опубликовано: 12 Апреля, 2022

If one wants an extension module that needs to pass a NULL-terminated string to a C library. Let’s see how to do it with Python’s Unicode string implementation. C libraries has many functions that operate on NULL-terminated strings declared as type char *.

В приведенном ниже коде есть функция C, которую мы проиллюстрируем и проверим. Функция C ( Код # 1 ) просто печатает шестнадцатеричное представление отдельных символов, чтобы переданные строки можно было легко отладить.

Code #1 :

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

Выход :

48 65 6c 6c 6f

 
To call such C function from Python, there are few choices. First of it is that – it can be restricted to only operate on bytes using “y” conversion code to PyArg_ParseTuple() as shown in the code below.

Code #2 :

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.

Code #3 :

print (print_chars(b"Hello World"))
  
print (" ", print_chars(b"Hellox00World"))
  
print (" ", print_chars("Hello World"))

Выход :

48 65 6c 6c 6f 20 57 6f 72 6c 64

Отслеживание (последний вызов последний):
Файл "", строка 1, в 
TypeError: должны быть байты без нулевых байтов, а не байты

Отслеживание (последний вызов последний):
Файл "", строка 1, в 
TypeError: 'str' не поддерживает интерфейс буфера

 
If you want to pass Unicode strings instead, use the “s” format code to PyArg_ParseTuple() as shown below.

Code #4 :

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;
}


Использование приведенного выше кода ( код # 4 ) автоматически преобразует все строки в кодировку UTF-8 с завершающим NULL. Как показано в приведенном ниже коде.

Код № 5:

print (print_chars("Hello World"))
  
# UTF-8 encoding
print (" ", print_chars("Spicy Jalapeu00f1o"))
   
print (" ", print_chars("Hellox00World"))
   
print (" ", print_chars(b"Hello World"))

Выход :

48 65 6c 6c 6f 20 57 6f 72 6c 64

53 70 69 63 79 20 4a 61 6c 61 70 65 c3 b1 6f

Отслеживание (последний вызов последний):
Файл "", строка 1, в 
TypeError: должно быть str без нулевых символов, а не str

Отслеживание (последний вызов последний):
Файл "", строка 1, в 
TypeError: должно быть str, а не байтами

 
If working with a PyObject * and can’t use PyArg_ParseTuple(), the code below explains how to check and extract a suitable char * reference, from both a bytes and string object.

Code #6 : Conversion from bytes

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

 
Code #7 : 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 где-либо еще внутри строки. Это нужно проверить, если это важно.

Note : There is a hidden memory overhead associated with using the “s” format code to PyArg_ParseTuple() that is easy to overlook. When writing a code that uses this conversion, a UTF-8 string is created and gets permanently attached to the original string object which if contains non-ASCII characters, makes the size of the string increase until it is garbage collected.

Code #8 :

import sys
s = "Spicy Jalapeu00f1o"
print ("Size : ", sys.getsizeof(s))
  
# passing string
print(" ", print_chars(s))
  
# increasing size
print (" Size : ", sys.getsizeof(s))

Выход :

Размер: 87

53 70 69 63 79 20 4a 61 6c 61 70 65 c3 b1 6f

Размер: 103

Внимание компьютерщик! Укрепите свои основы с помощью базового курса программирования Python и изучите основы.

Для начала подготовьтесь к собеседованию. Расширьте свои концепции структур данных с помощью курса Python DS. А чтобы начать свое путешествие по машинному обучению, присоединяйтесь к курсу Машинное обучение - базовый уровень.