Да, если основной код написан на 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 "может быть более ценится вашими пользователями.