Boost.Python从C ++创建对现有Python对象的新引用

时间:2014-11-21 11:10:36

标签: python c++ initialization boost-python

我使用Boost.Python包装C ++类X。目前创建了这个类的一个对象,我想在本地命名空间中插入一个对该对象的附加引用(这样我就可以通过一个固定的名称来引用他新创建的对象,让我们说{{ 1}})。我试过了 要使用

lastX构造函数中执行此操作
X::X()

但是这不起作用(X::X() { boost::python::object locals(boost::python::borrowed(PyEval_GetLocals())); boost::python::object me(this); locals["lastX"]=me; } 被创建但它没有引用;从Python打印导致段错误。我应该使用自己的lastX函数,但我不知道如何在那里获得对新创建的Python对象的引用。

有什么想法吗?感谢。

1 个答案:

答案 0 :(得分:2)

要实现此目的,必须修改调用堆栈上的另一个帧。请注意,这取决于Python的实现。例如,在Python 2.7中,inspect模块和sys.settrace()可用于修改特定帧上的locals()

我强烈建议使用Python解决方案,就像在this回答中所做的那样,并且猴子修补所需的类' __init__功能。例如,以下内容将修补Spam类以插入名为last_spam的变量,该变量将新构造的Spam实例引用到调用者的框架中:

def _patch_init(cls, name):
    cls_init = getattr(cls, '__init__', None)
    def patch(self):
        caller_frame = inspect.currentframe(1)
        new_locals = caller_frame.f_locals
        new_locals[name] = self
        _force_locals(caller_frame, new_locals)
        if cls_init:
            cls_init(self)
    setattr(cls, '__init__', patch)

_patch_init(Spam, 'last_spam')

spam = Spam()
assert(spam is last_spam)

然而,使用Boost.Python可以实现同样的目的。要做到这一点,必须:

  • 修改框架' locals()
  • 在对象构建期间访问self实例。

请注意,这些可能是相当先进的主题。

修改框架' s locals()

这与this回答中使用的方法相同,它取决于Python实现,但是用C ++编写。在大多数执行路径中,框架locals()不能将新变量写入其中。但是,启用系统跟踪时,调试器和其他工具经常使用的帧跟踪功能可以修改框架locals()

/// @brief Trace signature type.  Boost.Python requires MPL signature for
///        custom functors.
typedef boost::mpl::vector<
  boost::python::object, // return
  boost::python::object, // frame
  boost::python::object, // event
  boost::python::object // argt
> trace_signature_type;

/// @brief A noop function for Python.  Returns None.
boost::python::object noop(
  boost::python::tuple /* args */,
  boost::python::dict /* kw */
)
{
  return boost::python::object();
}

/// @brief Inject new_locals into the provided frame.
void inject_locals_into_frame(
  boost::python::object frame,
  boost::python::dict new_locals
)
{
  namespace python = boost::python;
  // Force tracing by setting the global tracing function to any non-None
  // function.
  // # if not sys.gettrace():
  if (!PyThreadState_Get()->c_tracefunc)
  {
    // Use the sys.settrace function to prevent needing to re-implement the
    // trace trampoline.
    //   # import sys
    python::object sys(python::handle<>(PyImport_ImportModule("sys")));
    //   # sys.settrace(lambda *args, **keys: None)
    sys.attr("__dict__")["settrace"](python::raw_function(&detail::noop));
  }

  // Create trace function.
  //   # def trace(frame, event, arg):
  python::object trace = python::make_function([new_locals](
        python::object frame,
        python::object /* event */,
        python::object /* arg */
      )
      {
        // Update the frame's locals.
        //   # frame.f_locals.update(new_locals)
        frame.attr("f_locals").attr("update")(new_locals);

        // Set the frame to use default trace, preventing this
        // trace functor from resetting the locals on each trace call.
        //   # del frame.f_trace
        frame.attr("f_trace").del();

        //   # return None
        return boost::python::object();
      },
    python::default_call_policies(),
    trace_signature_type());

  // Set the frame to use the custom trace.
  //   # frame.f_trace = trace
  frame.attr("f_trace") = trace;
}

使用上面的代码,可以使用inject_into_frame_locals()更新给定框架的本地人。例如,以下内容将添加一个引用x的变量42到当前帧中:

// Create dictionary that will be injected into the current frame.
namespace python = boost::python;
python::dict new_locals;
new_locals["x"] = 42;

// Get a handle to the current frame.
python::object frame(python::borrowed(
  reinterpret_cast<PyObject*>(PyEval_GetFrame())));

// Set locals to be injected into the frame.
inject_into_frame_locals(frame, new_locals);

在对象构建期间访问self实例。

给定C ++对象,不能使用Boost.Python API来定位保存对象的Python对象。因此,当Boost.Python正在构造对象时,必须访问self

有一些自定义点:

  • 修改在施工期间公开的类以接受PyObject*。可以让Boost.Python提供PyObject*实例专门化has_back_reference

    struct foo
    {
      // Constructor.
      foo(PyObject* self)
      {
        namespace python = boost::python;
        python::handle<> handle(python::borrowed(self));
        trace::inject_into_current_frame("last_foo", python::object(handle));
      }
    
      // Boost.Python copy constructor.
      foo(PyObject* self, const foo& /* rhs */)
      {
        namespace python = boost::python;
        python::handle<> handle(python::borrowed(self));
        trace::inject_into_current_frame("last_foo", python::object(handle));
      }
    };
    
    namespace boost {
    namespace python {
      // Have Boost.Python pass PyObject self to foo during construction.
      template <>
      struct has_back_reference<foo>
        : boost::mpl::true_
      {};
    } // namespace python
    } // namespace boost
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
      python::class_<foo>("Foo", python::init<>());
    }
    
  • T公开为由T派生的HeldType。如果HeldType是从T公开派生的,则在构建期间会提供PyObject*

    struct bar {};
    
    struct bar_holder
      : public bar
    {
      bar_holder(PyObject* self)
      {
       namespace python = boost::python;
        python::handle<> handle(python::borrowed(self));
        trace::inject_into_current_frame("last_bar", python::object(handle));
      }
    
      bar_holder(PyObject* self, const bar& /* rhs */)
      {
        namespace python = boost::python;
        python::handle<> handle(python::borrowed(self));
        trace::inject_into_current_frame("last_bar", python::object(handle));
     }
    };
    
    BOOST_PYTHON_MODULE(example)
    {
      namespace python = boost::python;
      python::class_<bar, bar_holder>("Bar", python::init<>());
    }
    
  • 禁止Boost.Python生成带有boost::python::no_init的默认初始化程序,然后使用自定义策略将自定义工厂函数注册为__init__方法。自定义对象构造的一个关键功能是boost::python::make_constructor功能。当提供指向C ++函数或指向成员函数的指针时,它将返回一个Python可调用对象,该对象在调用时将调用该函数并创建Python对象。但是,Boost.Python尝试从C ++函数中隐藏特定于Python的详细信息,因此未明确提供self。不过,可以使用自定义CallPolicy,并在precallpostcall函数中访问Python参数。为了访问self参数,必须了解具有make_constructor策略的实现细节,该策略将参数访问权限抵消1.例如,在尝试访问{时{1}}参数位于索引self,必须请求0索引。

    -1
  • 再次,可以抑制默认初始值设定项,并可以装饰// Mockup models. class spam {}; // Factor functions for the models will be necessary, as custom constructor // functions will be needed to provide a customization hook for our models. spam* make_spam() { return new spam(); } template <typename BasePolicy = boost::python::default_call_policies> struct custom_policy : BasePolicy { template <typename ArgumentPackage> PyObject* postcall(const ArgumentPackage& args, PyObject* result) { namespace python = boost::python; // Chain to base policy. result = BasePolicy::postcall(args, result); // self is the first argument. It is an implementation detail that // the make_constructor policy will offset access by 1. Thus, to // access the actual object at index 0 (self), one must use -1. python::object self(python::borrowed( get(boost::mpl::int_<-1>(), args))); return result; } }; BOOST_PYTHON_MODULE(example) { namespace python = boost::python; python::class_<spam>("Spam", python::no_init) .def("__init__", python::make_constructor( &make_spam, custom_policy<>())) ; } 返回的对象。调用make_constructor方法时,将调用装饰器对象,其中将提供初始化程序的所有参数,包括__init__。装饰器需要委托给self返回的对象。使用boost::python::make_function()将C ++仿函数转换为可调用的Python对象。请注意,make_constructor文档并未声明它支持自定义仿函数。但是,它在内部用于仿函数支持。

    make_function

以上是所有上述方法的完整示例demonstrating

// Mockup models.
class egg {};

// Factor functions for the models will be necessary, as custom constructor
// functions will be needed to provide a customization hook for our models.
egg* make_egg() { return new egg(); }

template <typename Fn>
class custom_constructor
{
public:

  typedef boost::python::object result_type;

public:

  custom_constructor(Fn fn)
    : constructor_(boost::python::make_constructor(fn))
  {}

  /// @brief Initialize python object.
  template <typename ...Args>
  result_type operator()(boost::python::object self, Args... args)
  {
    return constructor_(self, args...);
  }

private:
  boost::python::object constructor_;
};

template <typename Fn>
boost::python::object make_custom_constructor(Fn fn)
{
  // Use MPL to decompose the factory function signature into the
  // desired Python object signature.
  typedef /* ... */ signature_type;

  // Create a callable python object from custom_constructor.
  return boost::python::make_function(
    custom_constructor<Fn>(fn),
    boost::python::default_call_policies(),
    signature_type());
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<egg>("Egg", python::no_init)
    .def("__init__", make_custom_constructor(&make_egg))
    ;
}

交互式使用:

#include <boost/function_types/components.hpp>
#include <boost/mpl/insert_range.hpp>
#include <boost/python.hpp>
#include <boost/python/raw_function.hpp>

namespace trace {
namespace detail {

/// @brief Trace signature type.  Boost.Python requires MPL signature for
///        custom functors.
typedef boost::mpl::vector<
  boost::python::object, // return
  boost::python::object, // frame
  boost::python::object, // event
  boost::python::object  // arg
> trace_signature_type;

/// @brief A noop function for Python.  Returns None.
boost::python::object noop(
  boost::python::tuple /* args */,
  boost::python::dict /* kw */
)
{
  return boost::python::object();
}

} // namespace detail

/// @brief Inject new_locals into the provided frame.
void inject_into_frame_locals(
  boost::python::object frame,
  boost::python::dict new_locals
)
{
  namespace python = boost::python;
  // Force tracing by setting the global tracing function to any non-None
  // function.
  // # if not sys.gettrace():
  if (!PyThreadState_Get()->c_tracefunc)
  {
    // Use the sys.settrace function to prevent needing to re-implement the
    // trace trampoline.
    //   # import sys
    python::object sys(python::handle<>(PyImport_ImportModule("sys")));
    //   # sys.settrace(lambda *args, **keys: None)
    sys.attr("__dict__")["settrace"](python::raw_function(&detail::noop));
  }

  // Create trace function.
  //   # def trace(frame, event, arg):
  python::object trace = python::make_function([new_locals](
        python::object frame,
        python::object /* event */,
        python::object /* arg */
      )
      {
        // Update the frame's locals.
        //   # frame.f_locals.update(new_locals)
        frame.attr("f_locals").attr("update")(new_locals);

        // Set the frame to use default trace, preventing this
        // trace functor from resetting the locals on each trace call.
        //   # del frame.f_trace
        frame.attr("f_trace").del();

        //   # return None
        return boost::python::object();
      },
    python::default_call_policies(),
    detail::trace_signature_type());

  // Set the frame to use the custom trace.
  //   # frame.f_trace = trace
  frame.attr("f_trace") = trace;
}

/// @brief Helper function used to setup tracing to inject the key-value pair
///        into the current frame.
void inject_into_current_frame(
  std::string key,
  boost::python::object value)
{
  // If there is no key, just return early.
  if (key.empty()) return;

  // Create dictionary that will be injected into the current frame.
  namespace python = boost::python;
  python::dict new_locals;
  new_locals[key] = value;

  // Get a handle to the current frame.
  python::object frame(python::borrowed(
    reinterpret_cast<PyObject*>(PyEval_GetFrame())));

  // Set locals to be injected into the frame.
  inject_into_frame_locals(frame, new_locals);
}

} // namespace trace

/// APPROACH 1: has_back_reference

struct foo
{
  // Constructor.
  foo(PyObject* self)
  {
    namespace python = boost::python;
    python::handle<> handle(python::borrowed(self));
    trace::inject_into_current_frame("last_foo", python::object(handle));
  }

  // Boost.Python copy constructor.
  foo(PyObject* self, const foo& /* rhs */)
  {
    namespace python = boost::python;
    python::handle<> handle(python::borrowed(self));
    trace::inject_into_current_frame("last_foo", python::object(handle));
  }
};

namespace boost {
namespace python {
  // Have Boost.Python pass PyObject self to foo during construction.
  template <>
  struct has_back_reference<foo>
    : boost::mpl::true_
  {};
} // namespace python
} // namespace boost

/// APPROACH 2: custom holder

struct bar {};

struct bar_holder
  : public bar
{
  bar_holder(PyObject* self)
  {
    namespace python = boost::python;
    python::handle<> handle(python::borrowed(self));
    trace::inject_into_current_frame("last_bar", python::object(handle));
  }

  bar_holder(PyObject* self, const bar& /* rhs */)
  {
    namespace python = boost::python;
    python::handle<> handle(python::borrowed(self));
    trace::inject_into_current_frame("last_bar", python::object(handle));
  }
};

/// APPROACH 3: custom call policy

struct spam {};

/// @brief CallPolicy that injects a reference to the returned object
///        into the caller's frame.  Expected to only be used as a
//         policy for make_constructor.
template <typename BasePolicy = boost::python::default_call_policies>
struct inject_reference_into_callers_frame
  : BasePolicy
{
  inject_reference_into_callers_frame(const char* name)
    : name_(name)
  {}

  template <typename ArgumentPackage>
  PyObject* postcall(const ArgumentPackage& args, PyObject* result)
  {
    // Chain to base policy.
    result = BasePolicy::postcall(args, result);

    // self is the first argument.  It is an implementation detail that
    // the make_constructor policy will offset access by 1.  Thus, to 
    // access the actual object at index 0 (self), one must use -1.
    namespace python = boost::python;
    python::object self(python::borrowed(
      get(boost::mpl::int_<-1>(), args)));

    // Inject into the current frame. 
    trace::inject_into_current_frame(name_, self);

    return result;
  }

private:
  std::string name_;
};

// Factor functions for the models will be necessary, as custom constructor
// functions will be needed to provide a customization hook for our models.
spam* make_spam() { return new spam(); }

/// APPROACH 4: decorated constructor
//
struct egg {};

namespace detail {

/// @brief A constructor functor that injects the constructed object
///        into the caller's frame.
template <typename Fn>
class inject_constructor
{
public:

  typedef boost::python::object result_type;

public:

  /// @brief Constructor.
  inject_constructor(
    const char* name,
    Fn fn
  )
    : name_(name),
      constructor_(boost::python::make_constructor(fn))
  {}

  /// @brief Initialize the python objet.
  template <typename ...Args>
  result_type operator()(boost::python::object self, Args... args)
  {
    // Initialize the python object.
    boost::python::object result = constructor_(self, args...);

    // Inject a reference to self into the current frame.
    trace::inject_into_current_frame(name_, self);

    return result;
  }

private:
  std::string name_;
  boost::python::object constructor_;
};

} // namespace detail

/// @brief Makes a wrapper constructor (constructor that works with
///        classes inheriting from boost::python::wrapper).
template <typename Fn>
boost::python::object make_inject_constructor(
  const char* name,
  Fn fn)
{
  // Decompose the factory function signature, removing the return type.
  typedef typename boost::mpl::pop_front<
    typename boost::function_types::components<Fn>::type
  >::type components_type;

  // Python constructors take the instance/self argument as the first
  // argument, and returns None.  Thus, inject python::objects into the
  // signature type for both the return and 'self' argument.
  typedef typename boost::mpl::insert_range<
    components_type, 
    typename boost::mpl::begin<components_type>::type,
    boost::mpl::vector<boost::python::object, boost::python::object>
  >::type signature_type;

  // Create a callable python object from inject_constructor.
  return boost::python::make_function(
    detail::inject_constructor<Fn>(name, fn),
    boost::python::default_call_policies(),
    signature_type());
}

egg* make_egg() { return new egg(); }

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // APPROACH 1: has_back_reference
  python::class_<foo>("Foo", python::init<>());

  // APPROACH 2: custom holder
  python::class_<bar, bar_holder>("Bar", python::init<>());

  // APPROACH 3: custom call policy
  python::class_<spam>("Spam", python::no_init)
    .def("__init__", python::make_constructor(
      &make_spam,
      inject_reference_into_callers_frame<>("last_spam")))
    ;

  // APPROACH 4: decorated constructor
  python::class_<egg>("Egg", python::no_init)
    .def("__init__", make_inject_constructor("last_egg", &make_egg))
    ;
}