Pybind11:将 string* 参数传递给构造函数

Posted

技术标签:

【中文标题】Pybind11:将 string* 参数传递给构造函数【英文标题】:Pybind11: passing a string* argument to a constructor 【发布时间】:2018-05-10 18:18:51 【问题描述】:

在我不允许更改的 C++ 库中,我有一个如下所示的构造函数:

Dfa(const int n_state, const int dim_alf, const string *alf);

如果我只是简单地绑定

.def(py::init<const int, const int, const std::string*>())

编译成功。 问题是我不能通过 python 传递字符串*,因为例如如果我尝试在 python 上执行

alph=['x','y']
z=Dfa(3,2,alph)

它返回以下错误:

TypeError: __init__(): incompatible constructor arguments. The
following argument types are supported:
gi_gipy.Dfa(arg0: int, arg1: int, arg2: unicode)

用户“R zu”好心地建议我写一个包装器,但我不知道怎么做。鉴于 python 中的内容类似于:['x','y'], 在 c++ 中被接受为std::list&lt;std::string&gt; ,我尝试编写以下代码:

.def(py::init([](int n_state,int dim_alf, std::list<std::string> alph)
         std::string* alfabeto=new std::string[dim_alf];
         std::list<string>::iterator it=alph.begin();
         for(int i=0;it!=alph.end();++i,++it)  alfabeto[i]=*it;
         Dfa::Dfa(n_state,dim_alf,alfabeto);
))

但它返回给我 2 个错误:

cannot pass expression of type 'void' to variadic function
construct<Class>(v_h, func(std::forward<Args>(args)...)

static_assert failed "pybind11::init(): init function must return a compatible pointer,
  holder, or value"
static_assert(!std::is_same<Class, Class>::value /* always false */

很明显,我对如何克服这个问题有点困惑,我认为这与使用指向字符串的指针作为构造函数的参数有关。我再说一遍,我不能更改库,我只能创建适当的绑定。感谢您的关注

【问题讨论】:

我不确定你所说的字符串在 python 和 c++ (std) 中是否相同。这是一个带有隐藏参数的完整结构。也许切换到 char[] 会帮助您并避免这种定义差异。 不幸的是,我无法切换到 char[],因为我无法更改已实施的库 在调用这个lib函数之前,你可以使用函数c_str()和string()进行强制转换 如何在 c++ 端截取这个?我的疑问是,我在上面指定了 c++ 中的构造函数,我必须编写什么绑定 .def 才能绑定正确的构造函数并在 python 中拦截像 x=Dfa(3,2,['x','y']) 这样的调用? 你能不能在它们之间做一个包装函数,比如:Dfa_char(int a, int b, *char c) 调用Dfa(a, b, std::string(c))。你可以使用这个 Dfa_char 导入你的 python 代码中的任何内容 【参考方案1】:

main.cpp:

#include <iostream>
#include <list>
#include "pybind11/pybind11.h"
#include <pybind11/stl.h>
namespace py = pybind11;

class Dfa
public:
    Dfa(const int n_state, const std::size_t size, const std::string* alpha)
            : alpha_(*alpha) 
        std::cout << "n_state: " << n_state << "\n";
        std::cout << "size: " << size << "\n";
        std::cout << "*alpha: " << *alpha << "\n";
    
    // copy the std::string, not the reference or pointer.
    std::string alpha_; 
;

Dfa make_dfa(int n_state, std::string alpha) 
    // Copies the python unicode str to a local std::string
    // Modifying the local copy won't change the python
    // str.
    return Dfa(n_state, alpha.size(), &alpha);
    // Problem: Once the program leaves this function,
    // This local std::string is destroyed.
    // If the Dfa class just copies the pointer to this
    // std::string instead of the std::string, the Dfa
    // class will use a destroyed string.
    // Unless the Dfa object copies the string, this will
    // cause big trouble.


void print_char_list(std::list<char> alpha) 
    for (auto c: alpha) std::cout << c << ", ";
    std::cout << "\n";
    std::cout << "length of list is: " << alpha.size() << "\n";


PYBIND11_MODULE(_cpp, m) 
    py::class_<Dfa>(m, "Dfa")
            .def_readwrite("alpha", &Dfa::alpha_);;
    m.def("make_dfa", &make_dfa, "Create a Dfa object");
    m.def("print_char_list", &print_char_list, "Print a list of chars");

CMakeLists.txt:

cmake_minimum_required(VERSION 3.9)
project(test_pybind11)

set(CMAKE_CXX_STANDARD 11)

# Find packages.
set(PYTHON_VERSION 3)
find_package( PythonInterp $PYTHON_VERSION REQUIRED )
find_package( PythonLibs $PYTHON_VERSION REQUIRED )

# Download pybind11
set(pybind11_url https://github.com/pybind/pybind11/archive/stable.zip)

set(downloaded_file $CMAKE_BINARY_DIR/pybind11-stable.zip)
file(DOWNLOAD $pybind11_url $downloaded_file)
execute_process(COMMAND $CMAKE_COMMAND -E tar xzf $downloaded_file
        SHOW_PROGRESS)
file(REMOVE $downloaded_file)

set(pybind11_dir $CMAKE_BINARY_DIR/pybind11-stable)
add_subdirectory($pybind11_dir)
include_directories($pybind11_dir/include)

# Make python module
pybind11_add_module(_cpp main.cpp)

Python 3 测试:

>>> import _cpp
>>> s = "xyz"
>>> d = _cpp.make_dfa(1, s)
n_state: 1
size: 3
*alpha: xyz
>>> print(d.alpha)
xyz
>>> d.alpha = "abc"
>>> d.alpha
'abc'
>>> _cpp.print_char_list(['x', 'y', 'z'])
x, y, z, 
length of list is: 3

【讨论】:

我真的觉得Dfa的构造函数应该是Dfa(const int state, const std::string&amp; alpha)。不需要将字符串的长度传递给Dfa 的构造函数,因为std::string 已经包含此长度。 我尝试使用void dfa_wrapper(int n_state, std::string alpha) // Copy the python unicode string // and make a c++ std::string. // Modifying this copy won't change // the original python string. std::string* alph= new string[alpha.size()]; for(int i=0;i&lt;alpha.size();++i) alph[i]=alpha[i]; Dfa::Dfa(n_state, alpha.size(), alph); 并绑定m.def("Dfa", &amp;dfa_wrapper, "Wrapper of your Dfa::dfa");,但它返回错误ImportError: generic_type: cannot initialize type "Dfa": an object with that name is already defined 好吧。我制作的class Dfa 是您图书馆中Dfa 的演示,因为我不知道里面有什么。将我制作的Dfa替换为库中的Dfa 我认为这可能是由于我有多个名为 Dfa 的构造函数,例如我有一个由@987654334 绑定的默认空构造函数 Dfa()@always 感谢您的帮助! 我没有包含你的 Dfa(...) 示例代码,我正确地从我的库中调用了构造函数,这不是问题

以上是关于Pybind11:将 string* 参数传递给构造函数的主要内容,如果未能解决你的问题,请参考以下文章

如何将浮点数传递给期望 int 的 pybind11 函数

在函数-ggplot中将参数传递给构面网格

如何将数组作为参数传递给另一个脚本?

将 std::string ctor 参数传递给具有移动语义的 pimpl 中的 impl

pybind11 - 如果访问了结构的任何成员,则 boost::optional 结构的 std::vector 成员将被清空

从 powershell.exe 将参数传递给脚本