pybind11和所有权问题

huangapple go评论52阅读模式
英文:

pybind11 and ownership issues

问题

我正在为一个类编写 pybind11 的包装器,类似于元组,我希望它有一个 __iter__ 方法。

为了做到这一点,我将“元组”字段转换为变体的向量,并像这样定义“iter”:

cls.def(
  "__iter__", 
  [](const MyTuple& t){
      // 一些元编程来获取 MyTuple 的基础类型
      using my_variant = std::variant<...>;
      std::vector<my_variant> fields; // 本地变量!
      fill(fields); // 用 t 的字段填充,转换为 my_variant
      return py::make_iterator(begin(fields), end(fields));
  },
  py::keep_alive<0, 1>()
); 

当然,这是一个坏主意,因为 fields 一旦超出范围就会消失。

除了为 MyTuple 定义一个自定义持有者(封装这个向量),是否有一种简单的技巧可以将这个辅助向量的所有权传递给 pybind11?

如果我确实定义一个自定义持有者,是否可以从 py::class_ 访问它?

英文:

I'm writing a pybind11 wrapper for a tuple-like class, and I'd like it have __iter__ method.

To do this, I convert the "tuple" fields to a vector of variants and define &quot;__iter__&quot; like this:

cls.def(
  &quot;__iter__&quot;, 
  [](const MyTuple&amp; t){
      // some metaprogramming to get the underlying types of MyTuple
      using my_variant = std::variant&lt;...&gt;;
      std::vector&lt;my_variant&gt; fields; // local!
      fill(fields); // fill with t&#39;s fields converted to my_variant
      return py::make_iterator(begin(fields), end(fields));
  },
  py::keep_alive&lt;0, 1&gt;()
); 

Of course, this is a bad idea, because fields will die as soon as it goes out of scope.

Other than defining a custom holder for MyTuple (which would encapsulate this vector), is there a simple trick that could pass the ownership of this auxiliary vector to pybind11?

If I do define a custom holder, is it possible to access it from py::class_?

答案1

得分: 1

如果你的元组类可以与 std::apply 一起使用(或者你可以执行类似的操作),你可以简单地创建一个 Python 元组并返回其迭代器:

.def("__iter__", 
    [](const MyTuple& t){
        return py::iter(std::apply(
            [](auto&&... args) {
                return py::make_tuple(args...);
            },
            t));
    }) // 这里不需要使用 keep_alive

你还可以创建一个自定义迭代器,例如:

template <class Tuple>
struct tuple_iterator;

template <template <typename...> typename Tuple, typename... Args>
struct tuple_iterator<Tuple<Args...>> {
    using TupleType   = Tuple<Args...>;
    using VariantType = std::variant<std::add_pointer_t<std::add_const_t<Args>>...>;

    tuple_iterator() : index_{sizeof...(Args)}, values_{} {}

    tuple_iterator(TupleType const& tuple) : index_{0} {
        std::apply(
            [this](auto const&... args) {
                values_ = {VariantType{&args}...};
            },
            tuple.v);
    }

    VariantType operator*() const { return values_[index_]; }

    tuple_iterator& operator++() {
        ++index_;
        return *this;
    }

    friend bool operator==(tuple_iterator const& lhs, tuple_iterator const& rhs) {
        return lhs.index_ == rhs.index_;
    }

private:
    std::size_t index_;
    std::vector<VariantType> values_;
};

然后,你可以简单地:

.def("__iter__",
    [](MyTuple const& tuple) {
        return py::make_iterator(
            tuple_iterator<MyTuple>{tuple},
            tuple_iterator<MyTuple>{},
            py::return_value_policy::reference_internal);
    },
    py::keep_alive<0, 1>()
)

第二个版本的优点是不会复制对象。

英文:

If your tuple class can be used with std::apply (or if you can do something similar), you can simply create a Python tuple and return its iterator:

.def(&quot;__iter__&quot;, 
    [](const MyTuple&amp; t){
        return py::iter(std::apply(
            [](auto&amp;&amp;... args) {
                return py::make_tuple(args...);
            },
            t));
    }) // no need for keep_alive here

You could also create a custom iterator, e.g.

template &lt;class Tuple&gt;
struct tuple_iterator;

template &lt;template &lt;typename...&gt; typename Tuple, typename... Args&gt;
struct tuple_iterator&lt;Tuple&lt;Args...&gt;&gt; {
    using TupleType   = Tuple&lt;Args...&gt;;
    using VariantType = std::variant&lt;std::add_pointer_t&lt;std::add_const_t&lt;Args&gt;&gt;...&gt;;

    tuple_iterator() : index_{sizeof...(Args)}, values_{} {}

    tuple_iterator(TupleType const&amp; tuple) : index_{0} {
        std::apply(
            [this](auto const&amp;... args) {
                values_ = {VariantType{&amp;args}...};
            },
            tuple.v);
    }

    VariantType operator*() const { return values_[index_]; }

    tuple_iterator&amp; operator++() {
        ++index_;
        return *this;
    }

    friend bool operator==(tuple_iterator const&amp; lhs, tuple_iterator const&amp; rhs) {
        return lhs.index_ == rhs.index_;
    }

private:
    std::size_t index_;
    std::vector&lt;VariantType&gt; values_;
};

Then you can simply:

.def(&quot;__iter__&quot;,
    [](MyTuple const&amp; tuple) {
        return py::make_iterator(
            tuple_iterator&lt;MyTuple&gt;{tuple},
            tuple_iterator&lt;MyTuple&gt;{},
            py::return_value_policy::reference_internal);
    },
    py::keep_alive&lt;0, 1&gt;()
)

The second version has the advantage of not making copy of the objects.

huangapple
  • 本文由 发表于 2023年3月7日 02:36:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/75654595.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定