В чем разница между разными вариантами преобразования типов в pybind11? - PullRequest
1 голос
/ 02 февраля 2020

У меня есть проект, в котором я смешиваю cpp и python код.

По нескольким причинам внешний интерфейс должен быть в python, а внутренний - cpp.

Теперь я ищу решение о том, как передать мой python объект cpp. Следует обратить внимание на тот факт, что бэкэнд должен вызывать обратный вызов в python в некоторый момент для вычисления некоторых чисел, где функция python вернет список с плавающей точкой.

Я искал тип pybind параметры преобразования, определенные здесь: https://pybind11.readthedocs.io/en/stable/advanced/cast/index.html

Однако мне кажется, что варианты 1 довольно просты в использовании, как я вижу здесь: https://pybind11.readthedocs.io/en/stable/advanced/classes.html#overriding -virtual- functions-in- python

так что мне интересно, почему кто-то выбрал номер 3? как это сравнить с вариантом 1?

Большое спасибо

1 Ответ

1 голос
/ 02 февраля 2020

Да, если основной код написан на C ++, а привязки хорошо продуманы, тогда с вариантом 1 проще всего работать, так как в этом случае связанные объекты C ++ столь же естественны для использования в Python, как и нативные Python классы. Это облегчает жизнь, потому что вы получаете полный контроль над идентификацией объекта и возможностью его копирования.

Для 3, я считаю, что pybind11 слишком агрессивен с копированием при использовании обратных вызовов (как кажется, ваш вариант использования ), например, с массивами numpy вполне возможно работать с буфером на стороне C ++, если он проверен на смежность. Конечно, копирование защитит от проблем с памятью, но слишком мало контроля над копированием по сравнению с не копированием (numpy имеет те же проблемы с таблицами TBS).

Причина, по которой существует 3, заключается главным образом в том, что он улучшает удобство использования и обеспечивает хороший синтаксис. Например, если у нас есть функция с такой подписью:

void func(const std::vector<int>&)

, то было бы хорошо иметь возможность вызывать ее со стороны Python как func((1, 2, 3)) или даже func(range(3)). Это удобно, просто в использовании, выглядит чисто, и т. Д. c. Но на этом этапе нет другого выхода, кроме как копировать, так как структура памяти tuple настолько отличается от std::vector (а диапазон даже не представляет контейнер в памяти).

Обратите внимание, однако, что в приведенном выше примере func вызывающая сторона все еще может решить предоставить связанный объект std::vector<int> и, таким образом, предотвратить любое копирование. Может выглядеть не так красиво, но есть полный контроль. Это полезно, например, если вектор является возвращаемым значением какой-либо другой функции или изменяется между вызовами:

v = some_calc()   # with v a bound C++ vector
func(v)
v.append(4)       # add an element
func(v)

Сравните это со случаем, когда список чисел возвращается после вычисления некоторых чисел , аналог (но не совсем) вашего описания:

std::list<float> calc()

Если вы выберете «вариант 1», тогда связанная функция calc вернет связанный объект C ++ std::list<float>. Если вы выберете «опцию 3», связанная функция calc вернет Python list с скопированным в нее содержимым C ++ std::list<float>.

Проблема, которая возникает с опцией " 3 "заключается в том, что если вызывающий объект действительно хотел связанный объект C ++, то значения необходимо скопировать обратно в новый список, то есть всего 2 копии. OTOH, если вы выберете «вариант 1», а вызывающий абонент вместо этого потребует Python list, тогда он может сделать копию для возвращаемого значения calc, если необходимо:

res = calc()
list_res = list(res)

или даже, если они хотят этого все время:

def pycalc():
    return list(calc())

Теперь, наконец, к вашему конкретному c случаю, когда это обратный вызов Python, вызываемый из C ++, который возвращает список с плавающей точкой. Если вы используете «опцию 1», то функция Python будет вынуждена создать список C ++ для возврата, например, (с типом cpplist имя, присвоенное связанному типу std::list<float>):

def pycalc():
    return cpplist(range(3))

который программист Python не нашел бы симпатичным. Вместо этого, выбрав «вариант 3», проверив тип возврата и выполнив преобразование, если это необходимо, это также будет допустимо:

def pycalc():
    return [x for x in range(3)]

В зависимости от общих требований и типичных вариантов использования, затем «вариант 3 "может быть более ценится вашими пользователями.

...