簡體   English   中英

Boost.Python從C ++創建對現有Python對象的新引用

[英]Boost.Python create new reference to existing Python object from C++

我正在使用Boost.Python包裝C ++類X 在創建此類的對象時,我想在本地名稱空間中插入對該對象的附加引用(以便我可以使用固定名稱(例如lastX )引用他新創建的對象)。 我試圖在X::X()構造函數中使用

X::X() 
   {
   boost::python::object locals(boost::python::borrowed(PyEval_GetLocals()));
   boost::python::object me(this);
   locals["lastX"]=me;
   }

但這是行不通的( lastX被創建了,但是它什么也沒指;從Python打印會導致段錯誤)。 我可能應該使用自己的init函數,但是我也不知道該如何獲得對新創建的Python對象的引用。

有任何想法嗎? 謝謝。

為此,必須在調用堆棧上修改另一幀。 請注意,這取決於Python實現。 例如,在Python 2.7中,可以使用inspect模塊和sys.settrace()來修改特定幀上的locals()

我強烈建議使用Python解決方案,就像在答案中所做的那樣,並猴子修補所需類的__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實例。

請注意,這些可能是相當高級的話題。

修改框架的locals()

這與答案中使用的方法相同,該方法取決於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()更新給定幀的inject_into_frame_locals() 例如,以下將在當前幀中添加引用42的變量x

// 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提供專門has_back_referencePyObject*實例:

     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持有。 HeldTypeT公開派生時,將在構造過程中提供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生成默認的初始化boost::python::no_init ,然后使用自定義策略將自定義工廠函數注冊為__init__方法。 自定義對象構造的一項關鍵功能是boost::python::make_constructor函數。 當提供指向C ++函數或成員函數指針的指針時,它將返回Python可調用對象,該對象在被調用時將調用該函數並創建Python對象。 但是,Boost.Python試圖從C ++函數中隱藏特定於Python的詳細信息,因此沒有明確提供self 然而,可以使用AA定制CallPolicy ,並且無論是內部訪問的Python參數precallpostcall功能。 為了訪問self參數,必須了解make_constructor策略的實現細節,該策略make_constructor參數訪問偏移1。例如,當嘗試訪問位於索引0self參數時,必須請求-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可以修飾make_constructor返回的對象。 __init__方法被調用時,裝飾器對象將被調用,它將為初始化器提供所有參數,包括self 裝飾器將需要委托給make_constructor返回的對象。 使用boost::python::make_function()將C ++函子轉換為可調用的Python對象。 請注意, make_function文檔未聲明其支持自定義函子。 但是,它在內部用於函子支持。

     // 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))
    ;
}

互動用法:

>>> import example
>>> foo = example.Foo()
>>> assert(foo is last_foo)
>>> bar = example.Bar()
>>> assert(bar is last_bar)
>>> spam = example.Spam()
>>> assert(spam is last_spam)
>>> egg = example.Egg()
>>> assert(egg is last_egg)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM