![](/img/trans.png)
[英]Boost.Python C++ object reference in Python: unexpected behaviour
[英]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_reference
的PyObject*
實例:
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
生成默認的初始化boost::python::no_init
,然后使用自定義策略將自定義工廠函數注冊為__init__
方法。 自定義對象構造的一項關鍵功能是boost::python::make_constructor
函數。 當提供指向C ++函數或成員函數指針的指針時,它將返回Python可調用對象,該對象在被調用時將調用該函數並創建Python對象。 但是,Boost.Python試圖從C ++函數中隱藏特定於Python的詳細信息,因此沒有明確提供self
。 然而,可以使用AA定制CallPolicy ,並且無論是內部訪問的Python參數precall
或postcall
功能。 為了訪問self
參數,必須了解make_constructor
策略的實現細節,該策略make_constructor
參數訪問偏移1。例如,當嘗試訪問位於索引0
的self
參數時,必須請求-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.