переносимость Cython между объектами Unicode Python2 и Python3 - PullRequest
0 голосов
/ 12 июня 2018

Мне нужно написать высокопроизводительную логику обработки Unicode, и различия между python2 и объектом Unicode в python3 значительны.

Я только начинаю выяснять, как это сделать, и следующий фрагмент даетУ меня проблема:

from six.text_type import unicode
from cpython.version cimport PY_MAJOR_VERSION
cdef extern from "Python.h":
    int PyUnicode_KIND ( object o )
def unicode_size ( unicode u ):
    if PY_MAJOR_VERSION == 2:
        return sizeof ( Py_UNICODE )
    else:
        return PyUnicode_KIND ( u )

Этот код выполняется и выполняется как в Python 2, так и в 3. Однако компилятор Python2 c выдает «предупреждение C4013:« PyUnicode_KIND »не определено; предполагается, что extern возвращает int»

Обычно я мог бы подавить такого рода предупреждения, дав компилятору c внешнее объявление для функции, зная, что компоновщик вызовет истерику, если я на самом деле попытаюсь связать его.

Однако я могу 'Не могу понять, как создавать внешние объявления внутри Cython, чтобы компилятор был доволен.

Меня не интересует отключение предупреждения с помощью параметров командной строки, я стараюсь сделать компиляцию простой и понятной.вперед, и я один из тех "предупреждений об ошибках" фанатиков.Эта логика достаточно проста, чтобы поместиться в один файл pyx.Кроме того, преобладает мнение, что сгенерированный код C должен компилироваться как в Python 2, так и в Python 3, поэтому я стараюсь придерживаться этого.

Чтобы сделать вещи еще более нелепыми, когда я смотрю на сгенерированныйC-код, есть __Pyx_PyUnicode_KIND (), который делает именно то, что я хочу, но если я пытаюсь вызвать это из моего кода Cython, он говорит, что его не существует.Если вышесказанное невозможно, могу ли я получить доступ к этим макросам переносимости?

Может быть, я ни о чем не говорю.Очевидно, преждевременная оптимизация - это дьявол, но мой ограниченный опыт пока заключается в том, что для максимальной производительности мне нужно получить доступ к указателям за объектами python, иначе защитные оболочки абсолютно снижают производительность.Есть ли другой способ обработки ввода Unicode и создания другого вывода Unicode без обращения к специфическому для версии C apis?

---------- ОБНОВЛЕНИЕ ---------

Благодаря @ead я смог разработать решение, отвечающее всем моим критериям.Для всех, кто хочет накапливать символы UCS4 в буфере в Cython, а затем преобразовывать его в объект Unicode, когда это будет сделано, я решил эту проблему в своем файле .pyx:

cdef extern from *:
    """
    // This is C code that will be passed through to xmlwalk.c as-is:
    PyObject* PyUnicode_FromUCS4 ( Py_UCS4* s, Py_ssize_t size )
    {
#if PY_VERSION_HEX >= 0x03030000
        return PyUnicode_FromKindAndData ( PyUnicode_4BYTE_KIND, s, size );
#elif Py_UNICODE_SIZE == 4
        return PyUnicode_FromUnicode ( s, size );
#elif Py_UNICODE_SIZE == 2
        // WARNING: this version of the code rewrites s in-place as UTF-16.
        // `s` no longer contains valid UCS4 code points upon return.
        Py_UNICODE* dst = (Py_UNICODE*)s;
        Py_ssize_t dst_size = 0;
        Py_ssize_t i;
        for ( i = 0; i < size; i++ )
        {
            Py_UCS4 c = s[i];
            //printf ( "src[%i]=%i (0x%x)\\n", i, (int)c, (int)c );
            if ( c < 0x10000 )
            {
                // assert ( c < 0xD800 || c > 0xDFFF ); // disabled for performance reasons
                dst[dst_size++] = (Py_UNICODE)c;
                //printf ( "dst[%i]=%i (0x%x)\\n", dst_size-1, (int)dst[dst_size-1], (int)dst[dst_size-1] );
            }
            else
            {
                dst[dst_size++] = 0xD800 | (c >> 10);
                //printf ( "dst[%i]=%i (0x%x)\\n", dst_size-1, (int)dst[dst_size-1], (int)dst[dst_size-1] );
                dst[dst_size++] = 0xDC00 | (c & 0x3FF);
                //printf ( "dst[%i]=%i (0x%x)\\n", dst_size-1, (int)dst[dst_size-1], (int)dst[dst_size-1] );
            }
        }
        return PyUnicode_FromUnicode ( dst, dst_size );
#else
        assert(0); // could not determine correct unicode type
#endif
    }
    """
    PyObject* PyUnicode_FromUCS4 ( Py_UCS4* s, Py_ssize_t size )

1 Ответ

0 голосов
/ 12 июня 2018

Первое: вы должны заботиться о предупреждениях.

Вы должны знать, что сравнение PY_MAJOR_VERSION == 2 выполняется во время выполнения и не является директивой препроцессора, поэтому символ PyUnicode_KIND (в конце концов, в ANSI C (C89, C90) нетнужен прототип, компилятор делает вывод о прототипе, и поэтому расширение компилируется) может найти свой путь и в расширении для Python2.Это не происходит в сборке с включенной оптимизацией, потому что оптимизатор может видеть значение PY_MAJOR_VERSION во время компиляции и оптимизирует неправильную ветвь и, следовательно, также ссылку на PyUnicode_KIND.

Однако, если вы строите в отладке, без оптимизации на (-O0) сборка завершится неудачно.По крайней мере, в Linux - он будет построен - по умолчанию неопределенные символы разрешены в общих объектах, но во время импорта это не удастся, потому что загрузчик не найдет символ PyUnicode_KIND.Не уверен, что происходит в Windows, хотя ...

Я не думаю, что __Pyx_PyUnicode_KIND предполагается использовать в Cython (даже если это иногда удобно) - функция __Pyx_XXXX неупоминается в документации, поэтому они, вероятно, не предназначены для стабильного API и являются лишь деталями реализации.Однако вы можете неправильно использовать эту функцию для разрешения конфликтов имен :

cdef extern from *:  
    #put "__Pyx_PyUnicode_KIND" into the C-code, whenever my_PyUnicode_KIND is used:   
    int my_PyUnicode_KIND "__Pyx_PyUnicode_KIND" (object u) 

def unicode_size(u):
    my_PyUnicode_KIND(u)

Теперь, каждый раз, когда вы используете my_PyUnicode_KIND, Cython помещает __Pyx_PyUnicode_KIND в C-код.


Но, как уже говорилось, функции __Pyx_XXXX, вероятно, являются только деталями реализации и, следовательно, не стабильным API.Более стабильный подход будет следующим:

То, что вы на самом деле пытаетесь достичь, это следующий C-код, который использует препроцессор:

#include <Python.h>

//you might want to use  CYTHON_INLINE instead of inline
static inline int unicode_size(PyObject *o){
    //actually PyUnicode_KIND is defined since CPython3.3:
    #if PY_VERSION_HEX > 0x03030000 && defined(PyUnicode_KIND)
          return PyUnicode_KIND(o);
    #else
          return sizeof(Py_UNICODE);
    #endif
}

Для Python2 препроцессор выберет правильную ветвь исимвол PyUnicode_KIND никогда не попадет в созданный объектный файл.

Поскольку условные операторы в Cython работают немного по-другому (во-первых, PY_MAJOR_VERSION & Co не являются предопределенными именами времени компиляции иво-вторых, он не транслируется на C-препроцессоры, а только правильная ветвь транслируется на C), поэтому один из способов достижения вашей цели:

  1. Определите заголовок "unicodesize.h" с помощью кода извыше.
  2. Импортируйте его как обычно в Cython-модуль:

     cdef export from "unicodesize.h":
          int unicode_size(object o)  
    
  3. В зависимости от сборки вам может понадобиться добавить нужную папкук пути включения в файле установки.

Альтернативой может быть дословный код C-кода .

...