diff --git a/include/boost/python/class.hpp b/include/boost/python/class.hpp index fd1d7411..8ad4f857 100644 --- a/include/boost/python/class.hpp +++ b/include/boost/python/class.hpp @@ -290,19 +290,28 @@ class class_ : public objects::class_base // // Data member access // - template - self& def_readonly(char const* name, D B::*pm_) + template + self& def_readonly(char const* name, D const& d) { - D T::*pm = pm_; - this->add_property(name, make_getter(pm)); - return *this; + return this->def_readonly_impl(name, d, 0); } - template - self& def_readwrite(char const* name, D B::*pm_) + template + self& def_readwrite(char const* name, D const& d) { - D T::*pm = pm_; - return this->add_property(name, make_getter(pm), make_setter(pm)); + return this->def_readwrite_impl(name, d, 0); + } + + template + self& def_readonly(char const* name, D& d) + { + return this->def_readonly_impl(name, d, 0); + } + + template + self& def_readwrite(char const* name, D& d) + { + return this->def_readwrite_impl(name, d, 0); } // Property creation @@ -334,6 +343,34 @@ class class_ : public objects::class_base return *this; } + template + self& add_static_property(char const* name, Get fget) + { + base::add_static_property( + name + , object( + detail::member_function_cast::stage1(fget).stage2((T*)0).stage3(fget) + ) + ); + + return *this; + } + + template + self& add_static_property(char const* name, Get fget, Set fset) + { + base::add_static_property( + name + , object( + detail::member_function_cast::stage1(fget).stage2((T*)0).stage3(fget) + ) + , object( + detail::member_function_cast::stage1(fset).stage2((T*)0).stage3(fset) + ) + ); + return *this; + } + template self& setattr(char const* name, U const& x) { @@ -362,6 +399,32 @@ class class_ : public objects::class_base } private: // helper functions + template + self& def_readonly_impl(char const* name, D B::*pm_, int) + { + D T::*pm = pm_; + return this->add_property(name, make_getter(pm)); + } + + template + self& def_readwrite_impl(char const* name, D B::*pm_, int) + { + D T::*pm = pm_; + return this->add_property(name, make_getter(pm), make_setter(pm)); + } + + template + self& def_readonly_impl(char const* name, D& d, ...) + { + return this->add_static_property(name, make_getter(d)); + } + + template + self& def_readwrite_impl(char const* name, D& d, ...) + { + return this->add_static_property(name, make_getter(d), make_setter(d)); + } + inline void register_() const; // diff --git a/include/boost/python/data_members.hpp b/include/boost/python/data_members.hpp index 9c4574b4..a12a8b75 100644 --- a/include/boost/python/data_members.hpp +++ b/include/boost/python/data_members.hpp @@ -18,12 +18,16 @@ # include # include # include +# include # include # include # include +# include +# include # include +# include # include @@ -71,74 +75,169 @@ namespace detail } }; + template + struct datum + { + static PyObject* get(Data *p, PyObject* args_, PyObject*, Policies const& policies) + { + // find the result converter + typedef typename Policies::result_converter result_converter; + typedef typename boost::add_reference::type source; + typename mpl::apply1::type cr; + + if (!policies.precall(args_)) return 0; + + PyObject* result = cr( *p ); + + return policies.postcall(args_, result); + } + + static PyObject* set(Data* p, PyObject* args_, PyObject*, Policies const& policies) + { + // check that each of the arguments is convertible + typedef typename add_const::type target1; + typedef typename add_reference::type target; + arg_from_python c0(PyTuple_GET_ITEM(args_, 0)); + + if (!c0.convertible()) return 0; + + if (!policies.precall(args_)) return 0; + + *p = c0(PyTuple_GET_ITEM(args_, 0)); + + return policies.postcall(args_, detail::none()); + } + }; + + template + struct default_getter_by_ref + : mpl::and_< + mpl::bool_< + to_python_value< + typename add_reference::type>::type + >::uses_registry + > + , is_reference_to_class< + typename add_reference::type>::type + > + > + { + }; + // If it's a regular class type (not an object manager or other // type for which we have to_python specializations, use // return_internal_reference so that we can do things like // x.y.z = 1 // and get the right result. template - struct default_getter_policy + struct default_member_getter_policy + : mpl::if_< + default_getter_by_ref + , return_internal_reference<> + , return_value_policy + > + {}; + + template + struct default_datum_getter_policy + : mpl::if_< + default_getter_by_ref + , return_value_policy + , return_value_policy + > + {}; + + template + inline object make_getter(D* p, Policies const& policies, int) { - typedef typename add_reference< - typename add_const::type - >::type t_cref; + return objects::function_object( + ::boost::bind( + &detail::datum::get, p, _1, _2 + , policies) + , 0); + } - BOOST_STATIC_CONSTANT( - bool, by_ref = to_python_value::uses_registry - && is_reference_to_class::value); + template + inline object make_getter(D* p, not_specified, long) + { + typedef typename default_datum_getter_policy::type policies; + return make_getter(p, policies(), 0L); + } - typedef typename mpl::if_c< - by_ref - , return_internal_reference<> - , return_value_policy - >::type type; - }; -} - -template -object make_getter(D C::*pm) -{ - typedef typename detail::default_getter_policy::type policy; - - return objects::function_object( - ::boost::bind( - &detail::member::get, pm, _1, _2 - , policy()) - , 1); - -} - -template -object make_getter(D C::*pm, Policies const& policies) -{ + template + inline object make_getter(D C::*pm, Policies const& policies, int) + { return objects::function_object( ::boost::bind( &detail::member::get, pm, _1, _2 , policies) , 1); + } + + template + inline object make_getter(D C::*pm, not_specified, long) + { + typedef typename default_member_getter_policy::type policies; + return make_getter(pm, policies(), 0L); + } + + template + inline object make_getter(D& d, Policies const& policies, ...) + { + return detail::make_getter(&d, policies, 0L); + } + + template + inline object make_setter(D* p, Policies const& policies, long) + { + return objects::function_object( + ::boost::bind( + &detail::datum::set, p, _1, _2 + , policies) + , 1); + } + + template + inline object make_setter(D C::*pm, Policies const& policies, long) + { + return objects::function_object( + ::boost::bind( + &detail::member::set, pm, _1, _2 + , policies) + , 2); + } + + template + inline object make_setter(D& x, Policies const& policies, ...) + { + return detail::make_setter(&x, policies, 0L); + } } -template -object make_setter(D C::*pm) +template +inline object make_getter(D& d, Policies const& policies) { - return objects::function_object( - ::boost::bind( - &detail::member::set, pm, _1, _2 - , default_call_policies()) - , 2); + return detail::make_getter(d, policies, 0L); } -template -object make_setter(D C::*pm, Policies const& policies) +template +inline object make_getter(D& x) { - return objects::function_object( - ::boost::bind( - &detail::member::set, pm, _1, _2 - , policies) - , 2); + return detail::make_getter(x, detail::not_specified(), 0L); +} + +template +inline object make_setter(D& x, Policies const& policies) +{ + return detail::make_setter(x, policies, 0L); +} + +template +inline object make_setter(D& x) +{ + return detail::make_setter(x, default_call_policies(), 0L); } - }} // namespace boost::python #endif // DATA_MEMBERS_DWA2002328_HPP diff --git a/src/object/class.cpp b/src/object/class.cpp index ff14c9b7..637467eb 100644 --- a/src/object/class.cpp +++ b/src/object/class.cpp @@ -41,16 +41,160 @@ instance_holder::~instance_holder() { } -// This is copied from typeobject.c in the Python sources. Even though -// class_metatype_object doesn't set Py_TPFLAGS_HAVE_GC, that bit gets -// filled in by the base class initialization process in -// PyType_Ready(). However, tp_is_gc is *not* copied from the base -// type, making it assume that classes are GC-able even if (like -// class_type_object) they're statically allocated. -static int -type_is_gc(PyTypeObject *python_type) +extern "C" { - return python_type->tp_flags & Py_TPFLAGS_HEAPTYPE; + // This is copied from typeobject.c in the Python sources. Even though + // class_metatype_object doesn't set Py_TPFLAGS_HAVE_GC, that bit gets + // filled in by the base class initialization process in + // PyType_Ready(). However, tp_is_gc is *not* copied from the base + // type, making it assume that classes are GC-able even if (like + // class_type_object) they're statically allocated. + static int + type_is_gc(PyTypeObject *python_type) + { + return python_type->tp_flags & Py_TPFLAGS_HEAPTYPE; + } + + // This is also copied from the Python sources. We can't implement + // static_data as a subclass property effectively without it. + typedef struct { + PyObject_HEAD + PyObject *prop_get; + PyObject *prop_set; + PyObject *prop_del; + PyObject *prop_doc; + } propertyobject; + + static PyObject * + static_data_descr_get(PyObject *self, PyObject *obj, PyObject * /*type*/) + { + propertyobject *gs = (propertyobject *)self; + + return PyObject_CallFunction(gs->prop_get, "()"); + } + + static int + static_data_descr_set(PyObject *self, PyObject *obj, PyObject *value) + { + propertyobject *gs = (propertyobject *)self; + PyObject *func, *res; + + if (value == NULL) + func = gs->prop_del; + else + func = gs->prop_set; + if (func == NULL) { + PyErr_SetString(PyExc_AttributeError, + value == NULL ? + "can't delete attribute" : + "can't set attribute"); + return -1; + } + if (value == NULL) + res = PyObject_CallFunction(func, "()"); + else + res = PyObject_CallFunction(func, "(O)", value); + if (res == NULL) + return -1; + Py_DECREF(res); + return 0; + } +} + +static PyTypeObject static_data_object = { + PyObject_HEAD_INIT(0)//&PyType_Type) + 0, + "Boost.Python.class", + PyType_Type.tp_basicsize, + 0, + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT // | Py_TPFLAGS_HAVE_GC + | Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, //&PyProperty_Type, /* tp_base */ + 0, /* tp_dict */ + static_data_descr_get, /* tp_descr_get */ + static_data_descr_set, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, // filled in with type_new /* tp_new */ + 0, // filled in with __PyObject_GC_Del /* tp_free */ + (inquiry)type_is_gc, /* tp_is_gc */ +}; + +namespace objects +{ + extern "C" + { + // This declaration needed due to broken Python 2.2 headers + extern DL_IMPORT(PyTypeObject) PyProperty_Type; + } + + BOOST_PYTHON_DECL PyObject* static_data() + { + if (static_data_object.tp_dict == 0) + { + static_data_object.ob_type = &PyType_Type; + static_data_object.tp_base = &PyProperty_Type; + if (PyType_Ready(&static_data_object)) + return 0; + } + return upcast(&static_data_object); + } +} + +extern "C" +{ + // Ordinarily, descriptors have a certain assymetry: you can use + // them to read attributes off the class object they adorn, but + // writing the same attribute on the class object always replaces + // the descriptor in the class __dict__. In order to properly + // represent C++ static data members, we need to allow them to be + // written through the class instance. This function of the + // metaclass makes it possible. + static int + class_setattro(PyObject *obj, PyObject *name, PyObject* value) + { + // Must use "private" Python implementation detail + // _PyType_Lookup instead of PyObject_GetAttr because the + // latter will always end up calling the descr_get function on + // any descriptor it finds; we need the unadulterated + // descriptor here. + PyObject* a = _PyType_Lookup(downcast(obj), name); + + // a is a borrowed reference or 0 + + // If we found a static data descriptor, call it directly to + // force it to set the static data member + if (a != 0 && PyObject_IsInstance(a, objects::static_data())) + return a->ob_type->tp_descr_set(a, obj, value); + else + return PyType_Type.tp_setattro(obj, name, value); + } } static PyTypeObject class_metatype_object = { @@ -72,7 +216,7 @@ static PyTypeObject class_metatype_object = { 0, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ - 0, /* tp_setattro */ + class_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT // | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ @@ -110,6 +254,8 @@ void instance_holder::install(PyObject* self) throw() namespace objects { + static int (*class_setattro_save)(PyObject *obj, PyObject *name, PyObject* value); + // Get the metatype object for all extension classes. BOOST_PYTHON_DECL type_handle class_metatype() { @@ -119,6 +265,7 @@ namespace objects class_metatype_object.tp_base = &PyType_Type; if (PyType_Ready(&class_metatype_object)) return type_handle(); + class_setattro_save = class_metatype_object.tp_setattro; } return type_handle(borrowed(&class_metatype_object)); } @@ -189,6 +336,7 @@ namespace objects inst->dict = python::incref(dict); return 0; } + } @@ -254,6 +402,7 @@ namespace objects class_type_object.tp_base = &PyBaseObject_Type; if (PyType_Ready(&class_type_object)) return type_handle(); +// class_type_object.tp_setattro = class_setattro; } return type_handle(borrowed(&class_type_object)); } @@ -377,12 +526,6 @@ namespace objects this->attr("__instance_size__") = instance_size; } - extern "C" - { - // This declaration needed due to broken Python 2.2 headers - extern DL_IMPORT(PyTypeObject) PyProperty_Type; - } - void class_base::add_property(char const* name, object const& fget) { object property( @@ -401,6 +544,24 @@ namespace objects this->setattr(name, property); } + void class_base::add_static_property(char const* name, object const& fget) + { + object property( + (python::detail::new_reference) + PyObject_CallFunction(static_data(), "O", fget.ptr())); + + this->setattr(name, property); + } + + void class_base::add_static_property(char const* name, object const& fget, object const& fset) + { + object property( + (python::detail::new_reference) + PyObject_CallFunction(static_data(), "OO", fget.ptr(), fset.ptr())); + + this->setattr(name, property); + } + void class_base::setattr(char const* name, object const& x) { if (PyObject_SetAttrString(this->ptr(), const_cast(name), x.ptr()) < 0) diff --git a/test/data_members.cpp b/test/data_members.cpp index 13c46b0d..51765174 100644 --- a/test/data_members.cpp +++ b/test/data_members.cpp @@ -18,7 +18,11 @@ using namespace boost::python; typedef test_class<> X; -typedef test_class<1> Y; +struct Y : test_class<1> +{ + Y(int v) : test_class<1>(v) {} + Y& operator=(Y const& rhs) { x = rhs.x; return *this; } +}; double get_fair_value(X const& x) { return x.value(); } @@ -29,6 +33,7 @@ struct VarBase std::string const name; std::string get_name1() const { return name; } + }; struct Var : VarBase @@ -38,8 +43,14 @@ struct Var : VarBase float value; char const* name2; Y y; + + static int static1; + static Y static2; }; +int Var::static1 = 0; +Y Var::static2(0); + BOOST_PYTHON_MODULE(data_members_ext) { class_("X", init()) @@ -74,6 +85,17 @@ BOOST_PYTHON_MODULE(data_members_ext) .def("get_name2", &Var::get_name2, return_value_policy()) .add_property("name3", &Var::get_name1) + + // Test static data members + .def_readonly("ro1a", &Var::static1) + .def_readonly("ro1b", Var::static1) + .def_readwrite("rw1a", &Var::static1) + .def_readwrite("rw1b", Var::static1) + + .def_readonly("ro2a", &Var::static2) + .def_readonly("ro2b", Var::static2) + .def_readwrite("rw2a", &Var::static2) + .def_readwrite("rw2b", Var::static2) ; } diff --git a/test/data_members.py b/test/data_members.py index 989c3c32..f332ff76 100644 --- a/test/data_members.py +++ b/test/data_members.py @@ -1,6 +1,151 @@ ''' >>> from data_members_ext import * + ---- Test static data members --- + +>>> v = Var('slim shady') + +>>> Var.ro2a.x +0 +>>> Var.ro2b.x +0 +>>> Var.rw2a.x +0 +>>> Var.rw2b.x +0 +>>> v.ro2a.x +0 +>>> v.ro2b.x +0 +>>> v.rw2a.x +0 +>>> v.rw2b.x +0 +>>> Var.rw2a.x = 777 +>>> Var.ro2a.x +777 +>>> Var.ro2b.x +777 +>>> Var.rw2a.x +777 +>>> Var.rw2b.x +777 +>>> v.ro2a.x +777 +>>> v.ro2b.x +777 +>>> v.rw2a.x +777 +>>> v.rw2b.x +777 +>>> Var.rw2b = Y(888) +>>> Var.ro2a.x +888 +>>> Var.ro2b.x +888 +>>> Var.rw2a.x +888 +>>> Var.rw2b.x +888 +>>> v.ro2a.x +888 +>>> v.ro2b.x +888 +>>> v.rw2a.x +888 +>>> v.rw2b.x +888 +>>> v.rw2b.x = 999 +>>> Var.ro2a.x +999 +>>> Var.ro2b.x +999 +>>> Var.rw2a.x +999 +>>> Var.rw2b.x +999 +>>> v.ro2a.x +999 +>>> v.ro2b.x +999 +>>> v.rw2a.x +999 +>>> v.rw2b.x +999 + + +>>> Var.ro1a +0 +>>> Var.ro1b +0 +>>> Var.rw1a +0 +>>> Var.rw1b +0 +>>> v.ro1a +0 +>>> v.ro1b +0 +>>> v.rw1a +0 +>>> v.rw1b +0 +>>> Var.rw1a = 777 +>>> Var.ro1a +777 +>>> Var.ro1b +777 +>>> Var.rw1a +777 +>>> Var.rw1b +777 +>>> v.ro1a +777 +>>> v.ro1b +777 +>>> v.rw1a +777 +>>> v.rw1b +777 +>>> Var.rw1b = 888 +>>> Var.ro1a +888 +>>> Var.ro1b +888 +>>> Var.rw1a +888 +>>> Var.rw1b +888 +>>> v.ro1a +888 +>>> v.ro1b +888 +>>> v.rw1a +888 +>>> v.rw1b +888 +>>> v.rw1b = 999 +>>> Var.ro1a +999 +>>> Var.ro1b +999 +>>> Var.rw1a +999 +>>> Var.rw1b +999 +>>> v.ro1a +999 +>>> v.ro1b +999 +>>> v.rw1a +999 +>>> v.rw1b +999 + + + + ----------------- + >>> x = X(42) >>> x.x 42 @@ -39,6 +184,7 @@ >>> v.name3 'pi' + ''' def run(args = None):