提升python容器,迭代器和项目生命周期

时间:2012-11-27 13:43:50

标签: c++ python boost-python

我正在尝试将C ++容器暴露给Python。 我有:

class Container {
    std::auto_ptr<Iterator> __iter__();
};

class Iterator {
    Container & parent;
    Item __next__();
};

class Item {
    Container & parent;
};

Item类在内部引用Container中存在的数据。返回Iterator实例的Item不一定存在,Item可用。

c = Container()
for i in c:
    store = i

print store

在上面的代码中,我希望获得ContainerIterator和少量Item个实例。 当它达到print语句时,我希望Iterator已经被销毁,但Container显然仍然存在store个实例。

现在问题来了。我不知道用CallPolicy来实现这种效果: 定义:

class_<Container>("Container", ...)
  .def("__iter__", &Container::__iter__, return_interal_reference<>() )
;

class_<Iterator>("Iterator", ...)
  .def("next", &Iterator::__next__, what_call_policy_here? )
;

class_<Item>("Item", ...)
  .def("__str__", ... )
;

我应该使用什么代替what_call_policy_here

1 个答案:

答案 0 :(得分:2)

好的,经过长时间的挖掘后,我想我想出了一个对暴露类型透明的解决方案。

简短说明

基本上解决方案是创建CallPolicy,它将自动存储对返回的对象内的对象(即Container)的引用(即{ {1}})作为它的属性(我使用了私有名称,但Python在这方面非常自由)。

然后自动将其复制到所有兄弟对象(兄弟的另一个,但是一个是通过调用另一个的方法创建的,因此不是直接来自 parent )。

实施

这需要摆弄Iterator。我必须创建两个自定义的:

CallPolicy

用法

现在用法:

// This policy is used for methods returning items that require object to be
// kept as long as return thing is alive.
// It stores reference in attribute named Property_::name
template <typename Property_, class BasePolicy_ = boost::python::default_call_policies>
struct store_parent_reference: public BasePolicy_
{
    template <class ArgumentPackage>
    static PyObject* postcall(ArgumentPackage const& args_, PyObject* result)
    {
        result = BasePolicy_::postcall(args_, result);

        PyObject* parent = detail::get_prev< std::size_t(1) >::execute(args_, result);
        PyObject* child = result;

        if( PyObject_SetAttrString( child, Property_::name, parent ) == -1 )
        {
            std::ostringstream err;
            err << "store_parent_reference::postcall could not set attribute `"                    << Property_::name
                << "` on newly allocated object `"
                << extract<std::string>( object( handle<>(borrowed(child))).attr("__str__")() )()
                << "`";
            throw std::runtime_error(err.str());
        }



        return result;
    }
};


// This policy is used for methods returning "sibling" in the meaning both the returned object
// and one that has this method called on require "parent" object to be alive.
//
// It copies reference to "parent" to attribute named ChildProperty_::name
// from "original" object's attribute named SiblingProperty_::name
template <typename ChildProperty_, typename SiblingProperty_ = ChildProperty_, class BasePolicy_ = boost::python::default_call_policies>
struct copy_parent_from_sibling: public BasePolicy_
{
    template <class ArgumentPackage>
    static PyObject* postcall(ArgumentPackage const& args_, PyObject* result)
    {
        result = BasePolicy_::postcall(args_, result);

        PyObject* sibling = detail::get_prev< std::size_t(1) >::execute(args_, result);
        PyObject* new_child = result;

        PyObject* parent = PyObject_GetAttrString( sibling, SiblingProperty_::name );

        if( parent == NULL )
        {
            std::ostringstream err;
            err << "copy_parent_from_sibling::postcall could not get attribute `"
                << SiblingProperty_::name
                << "` from sibling `"
                << extract<std::string>( object( handle<>(borrowed(sibling))).attr("__str__")() )()
                << "` to set up attribute `"
                << ChildProperty_::name
                << "` of returned object which is `"
                << extract<std::string>( object( handle<>(borrowed(new_child))).attr("__str__")() )()
                << "`";
            throw std::runtime_error(err.str());
        }

        if( PyObject_SetAttrString( new_child, ChildProperty_::name, parent ) == -1 )
        {
            std::ostringstream err;
            err << "copy_parent_from_sibling::postcall could not set attribute `"
                << ChildProperty_::name
                << "` on returned object which is `"
                << extract<std::string>( object( handle<>(borrowed(new_child))).attr("__str__")() )()
                << "`";
            throw std::runtime_error(err.str());
        }

        Py_DECREF(parent);

        return result;
    }
};

注意:很难为struct ContainerProperty { static const char * const name; }; const char * const ContainerProperty::name = "__container" class_<Container>("Container", ...) .def("__iter__", &Container::__iter__, store_parent_reference< ContainerProperty >() ) ; class_<Iterator>("Iterator", ...) .def("next", &Iterator::__next__, copy_parent_from_sibling< ContainerProperty >() ) ; class_<Item>("Item", ...) ; 内容提供完整的最小工作样本,所以我可能错过了上面的一些细节,但解决方案似乎对我很好(我是跟踪析构函数调用以检查)。

这也不是唯一的解决方案。请注意,boost::python有点类似于store_parent_reference,区别在于它明确需要存储数据的位置。这只是因为return_internal_reference需要从某处复制它。

这种方法的主要好处是它不需要原始类来了解Python的东西。