Как подключить C библиотеку к питону, заюзать NumPy и не выстрелить в ногу

Максим Кольцов

Работа с Python C API

  • SWIG
  • SIP (PyQt)
  • shiboken2 (PySide2)
  • Boost::Python
  • pybind11

Проблемы

  • Сборка
  • Преобразования типов
  • Исключения
  • GC
  • GIL
  • Вызов виртуальных методов
  • Наследование в Python

Как компилировать:

CMake

set(CMAKE_CXX_STANDARD 11)
find_package(PythonInterp 3.4 REQUIRED)
find_package(PythonLibs 3.4 REQUIRED)
find_package(SIP REQUIRED)
include(SIPMacros)

set(SIP_CONCAT_PARTS 4)
set(SIP_EXTRA_OPTIONS -e -o -y module.pyi)

set(SIP_SOURCES src/module.sip ...)

set(SIP_EXTRA_FILES_DEPEND ${SIP_SOURCES})
generate_sip_python_module_code(
    _module 
    src/module.sip
    py_cpp_files)
build_sip_python_module(
    _module
    src/module.sip
    "${py_cpp_files}")

А если NumPy?

list(GET py_cpp_files 0 PART0)
set_source_files_properties(${PART0}
    PROPERTIES COMPILE_DEFINITIONS SIP_IS_PART0)
%UnitPostIncludeCode
    #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
    #define PY_ARRAY_UNIQUE_SYMBOL module_ARRAY_API

    #ifndef SIP_IS_PART0
    #define NO_IMPORT_ARRAY
    #endif

    #include <numpy/arrayobject.h>
%End

%InitialisationCode
    import_array();
%End

GC в Python C API

Py_INCREF / Py_DECREF

C++:

    void init(int count,
              int lengths[],
              int* docs[]);
    void doSomething();

Python:

    init(docs: List[List[int]])
    doSomething()

Как передать int** и не потерять память?

Py_ssize_t documentCount = PyList_Size(docs);
std::unique_ptr<int[]> lengths { new int[documentCount] };
std::unique_ptr<int*[]> docPtrs { new int*[documentCount] };
PyObject *docsList = PyList_New(documentCount);

for (Py_ssize_t i = 0; i < documentCount; i++) {
    PyObject *item = PyList_GetItem(docs, i);
    PyObject *document = PyArray_FROMANY(
        item,
        NPY_UINT32,
        1, 1,
        NPY_ARRAY_C_CONTIGUOUS
    );
    if (document == nullptr) {
        sipIsErr = 1;
        break;
    }

    lengths[i] = PyArray_SHAPE((PyArrayObject*) document)[0];
    docPtrs[i] = (int*) PyArray_DATA((PyArrayObject*) document);

    PyList_SetItem(docsList, i, document);
}

sipKeepReference(sipSelf, (int) time(nullptr), docsList);

GIL!

Py_BEGIN_ALLOW_THREADS
sipRes = sipCpp->init(
    documentCount,
    lengths.get(), docPtrs.get()
);
Py_END_ALLOW_THREADS
PyObject* wrapMatrix(
    double *matrix,
    int d1, int d2,
    PyObject *sipPySelf)
{
    npy_intp dims[] = { d1, d2 };

    return PyArray_SimpleNewFromData(2, dims, NPY_DOUBLE, matrix);
}

И обратно

Дополнительная безопасность

  • Valgrind
  • Address Sanitizer
  • Thread Sanitizer
  • Собрать свой питон:

    • CFLAGS='-g -ggdb -fsanitize=address' ./configure
        --with-pydebug
        --without-pymalloc
  • Инжектить libasan через LD_PRELOAD

Как подключить C библиотеку к питону, заюзать NumPy и не выстрелить в ногу"

By Maxim Koltsov

Как подключить C библиотеку к питону, заюзать NumPy и не выстрелить в ногу"

  • 1,629