From d100de1a5bcbc7585f5044d27f5195d8d0cb55f9 Mon Sep 17 00:00:00 2001 From: "Gustavo J. A. M. Carneiro" Date: Sun, 9 Sep 2007 23:16:25 +0100 Subject: [PATCH] Fix Bug #138344: Generic C++ functions can't be assigned to a method in python. --- examples/foo.cc | 15 +++++++ examples/foo.h | 6 +++ examples/foomodulegen.py | 21 +++++++++ examples/footest.py | 12 +++++ pybindgen/cppclass.py | 96 +++++++++++++++++++++++++++------------- pybindgen/function.py | 9 +++- 6 files changed, 127 insertions(+), 32 deletions(-) diff --git a/examples/foo.cc b/examples/foo.cc index c3336e3..37ff17d 100644 --- a/examples/foo.cc +++ b/examples/foo.cc @@ -132,3 +132,18 @@ SomeObject * set_foobar_with_return_as_custodian(Foobar *foobar) foobar++; return new SomeObject("xxx"); } + +std::string some_object_get_something_prefixed(const SomeObject *obj, const std::string something) +{ + return obj->get_prefix() + something; +} + +std::string some_object_val_get_something_prefixed(SomeObject obj, const std::string something) +{ + return obj.get_prefix() + something; +} + +std::string some_object_ref_get_something_prefixed(const SomeObject &obj, const std::string something) +{ + return obj.get_prefix() + something; +} diff --git a/examples/foo.h b/examples/foo.h index 33c8bee..4f7a750 100644 --- a/examples/foo.h +++ b/examples/foo.h @@ -294,6 +294,12 @@ public: }; +// A function that will appear as a method of SomeObject +// obj: transfer_ownership=false +std::string some_object_get_something_prefixed(const SomeObject *obj, const std::string something); + +std::string some_object_val_get_something_prefixed(SomeObject obj, const std::string something); +std::string some_object_ref_get_something_prefixed(const SomeObject &obj, const std::string something); // Transfer ownership of 'obj' to the library diff --git a/examples/foomodulegen.py b/examples/foomodulegen.py index 05a6307..3962adc 100755 --- a/examples/foomodulegen.py +++ b/examples/foomodulegen.py @@ -152,6 +152,27 @@ def my_module_gen(out_file): is_virtual=True, is_const=True)) + ## add a function that appears as a method of an object + SomeObject.add_method( + Function(ReturnValue.new('std::string'), 'some_object_get_something_prefixed', + [Parameter.new('SomeObject*', 'obj', transfer_ownership=False, is_const=True), + Parameter.new('std::string', 'something')]), + name='get_something_prefixed') + + ## add a function that appears as a method of an object + SomeObject.add_method( + Function(ReturnValue.new('std::string'), 'some_object_val_get_something_prefixed', + [Parameter.new('SomeObject', 'obj'), + Parameter.new('std::string', 'something')]), + name='val_get_something_prefixed') + + ## add a function that appears as a method of an object + SomeObject.add_method( + Function(ReturnValue.new('std::string'), 'some_object_ref_get_something_prefixed', + [Parameter.new('SomeObject&', 'obj', is_const=True), + Parameter.new('std::string', 'something')]), + name='ref_get_something_prefixed') + # --- SomeObject.add_method(CppMethod( ReturnValue.new('std::string'), 'call_get_prefix', [])) diff --git a/examples/footest.py b/examples/footest.py index bba69cb..7260d79 100644 --- a/examples/footest.py +++ b/examples/footest.py @@ -543,6 +543,18 @@ class TestFoo(unittest.TestCase): f2 = obj.foo self.assertEqual(f2.get_datum(), "hello") + def test_function_as_method(self): + obj = foo.SomeObject("xpto") + self.assertEqual(obj.get_something_prefixed("something"), "xptosomething") + + def test_function_as_method_val(self): + obj = foo.SomeObject("xpto") + self.assertEqual(obj.val_get_something_prefixed("something"), "xptosomething") + + def test_function_as_method_ref(self): + obj = foo.SomeObject("xpto") + self.assertEqual(obj.ref_get_something_prefixed("something"), "xptosomething") + if __name__ == '__main__': unittest.main() diff --git a/pybindgen/cppclass.py b/pybindgen/cppclass.py index 471d408..d962b2f 100644 --- a/pybindgen/cppclass.py +++ b/pybindgen/cppclass.py @@ -14,6 +14,7 @@ from cppmethod import CppMethod, CppConstructor, CppNoConstructor, \ from cppattribute import (CppInstanceAttributeGetter, CppInstanceAttributeSetter, CppStaticAttributeGetter, CppStaticAttributeSetter, PyGetSetDef, PyMetaclass) + import settings class CppHelperClass(object): @@ -416,9 +417,19 @@ public: from Python side """ assert name is None or isinstance(name, str) - assert isinstance(method, CppMethod) - if name is None: - name = method.method_name + if isinstance(method, CppMethod): + if name is None: + name = method.method_name + elif isinstance(method, function.Function): + assert name is not None + assert isinstance(method.parameters[0], CppClassParameterBase) + assert method.parameters[0].cpp_class is self + method.parameters[0].take_value_from_python_self = True + method.module = self.module + method.is_virtual = False + method.self_parameter_pystruct = self.pystruct + else: + raise TypeError try: overload = self.methods[name] @@ -780,6 +791,11 @@ class CppClassParameterBase(Parameter): ## name of the PyFoo * variable used in parameter parsing self.py_name = None + ## it True, this parameter is 'fake', and instead of being + ## passed a parameter from python it is assumed to be the + ## 'self' parameter of a method wrapper + self.take_value_from_python_self = False + class CppClassReturnValueBase(ReturnValue): "Class return handlers -- base class" @@ -802,13 +818,15 @@ class CppClassParameter(CppClassParameterBase): "parses python args to get C++ value" assert isinstance(wrapper, ForwardWrapperBase) assert isinstance(self.cpp_class, CppClass) - name = wrapper.declarations.declare_variable( - self.cpp_class.pystruct+'*', self.name) - self.py_name = name - wrapper.parse_params.add_parameter( - 'O!', ['&'+self.cpp_class.pytypestruct, '&'+name], self.name) + if self.take_value_from_python_self: + self.py_name = 'self' + else: + self.py_name = wrapper.declarations.declare_variable( + self.cpp_class.pystruct+'*', self.name) + wrapper.parse_params.add_parameter( + 'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name) wrapper.call_params.append( - '*((%s *) %s)->obj' % (self.cpp_class.pystruct, name)) + '*((%s *) %s)->obj' % (self.cpp_class.pystruct, self.py_name)) def convert_c_to_python(self, wrapper): '''Write some code before calling the Python method.''' @@ -854,31 +872,38 @@ class CppClassRefParameter(CppClassParameterBase): assert isinstance(wrapper, ForwardWrapperBase) assert isinstance(self.cpp_class, CppClass) - name = wrapper.declarations.declare_variable( - self.cpp_class.pystruct+'*', self.name) - self.py_name = name if self.direction == Parameter.DIRECTION_IN: - wrapper.parse_params.add_parameter( - 'O!', ['&'+self.cpp_class.pytypestruct, '&'+name], self.name) - wrapper.call_params.append('*%s->obj' % (name,)) + if self.take_value_from_python_self: + self.py_name = 'self' + else: + self.py_name = wrapper.declarations.declare_variable( + self.cpp_class.pystruct+'*', self.name) + wrapper.parse_params.add_parameter( + 'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name) + wrapper.call_params.append('*%s->obj' % (self.py_name,)) elif self.direction == Parameter.DIRECTION_OUT: + assert not self.take_value_from_python_self + + self.py_name = wrapper.declarations.declare_variable( + self.cpp_class.pystruct+'*', self.name) + if self.cpp_class.allow_subclassing: new_func = 'PyObject_GC_New' else: new_func = 'PyObject_New' wrapper.before_call.write_code( "%s = %s(%s, %s);" % - (name, new_func, self.cpp_class.pystruct, + (self.py_name, new_func, self.cpp_class.pystruct, '&'+self.cpp_class.pytypestruct)) if self.cpp_class.allow_subclassing: wrapper.after_call.write_code( - "%s->inst_dict = NULL;" % (name,)) + "%s->inst_dict = NULL;" % (self.py_name,)) wrapper.before_call.write_code( - "%s->obj = new %s;" % (name, self.cpp_class.full_name)) - wrapper.call_params.append('*%s->obj' % (name,)) - wrapper.build_params.add_parameter("N", [name]) + "%s->obj = new %s;" % (self.py_name, self.cpp_class.full_name)) + wrapper.call_params.append('*%s->obj' % (self.py_name,)) + wrapper.build_params.add_parameter("N", [self.py_name]) ## well, personally I think inout here doesn't make much sense ## (it's just plain confusing), but might as well support it.. @@ -887,10 +912,15 @@ class CppClassRefParameter(CppClassParameterBase): ## modifications, i.e. the object is not explicitly returned ## but is instead modified by the callee. elif self.direction == Parameter.DIRECTION_INOUT: + assert not self.take_value_from_python_self + + self.py_name = wrapper.declarations.declare_variable( + self.cpp_class.pystruct+'*', self.name) + wrapper.parse_params.add_parameter( - 'O!', ['&'+self.cpp_class.pytypestruct, '&'+name], self.name) + 'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name) wrapper.call_params.append( - '*%s->obj' % (name)) + '*%s->obj' % (self.py_name)) def convert_c_to_python(self, wrapper): '''Write some code before calling the Python method.''' @@ -1035,22 +1065,25 @@ class CppClassPtrParameter(CppClassParameterBase): "parses python args to get C++ value" assert isinstance(wrapper, ForwardWrapperBase) assert isinstance(self.cpp_class, CppClass) - name = wrapper.declarations.declare_variable( - self.cpp_class.pystruct+'*', self.name) - self.py_name = name - wrapper.parse_params.add_parameter( - 'O!', ['&'+self.cpp_class.pytypestruct, '&'+name], self.name) + + if self.take_value_from_python_self: + self.py_name = 'self' + else: + self.py_name = wrapper.declarations.declare_variable( + self.cpp_class.pystruct+'*', self.name) + wrapper.parse_params.add_parameter( + 'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name) value = self.transformation.transform( - self, wrapper.declarations, wrapper.before_call, '%s->obj' % name) + self, wrapper.declarations, wrapper.before_call, '%s->obj' % self.py_name) wrapper.call_params.append(value) if self.transfer_ownership: if self.cpp_class.incref_method is None: - wrapper.after_call.write_code('%s->obj = NULL;' % (name,)) + wrapper.after_call.write_code('%s->obj = NULL;' % (self.py_name,)) else: wrapper.before_call.write_code('%s->obj->%s();' % ( - name, self.cpp_class.incref_method,)) + self.py_name, self.cpp_class.incref_method,)) def convert_c_to_python(self, wrapper): @@ -1387,3 +1420,6 @@ def implement_parameter_custodians(wrapper): assert custodian_param.cpp_class.allow_subclassing _add_ward(wrapper, "((PyObject *) %s)" % custodian_param.py_name, "((PyObject *) %s)" % param.py_name) + + +import function diff --git a/pybindgen/function.py b/pybindgen/function.py index d6a351b..33e02c9 100644 --- a/pybindgen/function.py +++ b/pybindgen/function.py @@ -7,7 +7,6 @@ from copy import copy from typehandlers.base import ForwardWrapperBase from typehandlers import codesink import overloading -import cppclass class Function(ForwardWrapperBase): @@ -30,6 +29,7 @@ class Function(ForwardWrapperBase): self.wrapper_base_name = None self.wrapper_actual_name = None self.docstring = docstring + self.self_parameter_pystruct = None def clone(self): """Creates a semi-deep copy of this function wrapper. The returned @@ -90,7 +90,11 @@ class Function(ForwardWrapperBase): python_args = '' flags = self.get_py_method_def_flags() if 'METH_VARARGS' in flags: - python_args += "PyObject * PYBINDGEN_UNUSED(dummy), PyObject *args" + if self.self_parameter_pystruct is None: + self_param = 'PyObject * PYBINDGEN_UNUSED(dummy)' + else: + self_param = '%s *self' % self.self_parameter_pystruct + python_args += "%s, PyObject *args" % self_param if 'METH_KEYWORDS' in flags: python_args += ", PyObject *kwargs" @@ -123,3 +127,4 @@ class OverloadedFunction(overloading.OverloadedWrapper): RETURN_TYPE = 'PyObject *' ERROR_RETURN = 'return NULL;' +import cppclass