Opened 14 years ago

Closed 14 years ago

Last modified 14 years ago

#2049 closed Bugs (invalid)

extracting c++ pointer from python object with multiple parents

Reported by: bloodyfanatic@… Owned by: Dave Abrahams
Milestone: Component: python USE GITHUB
Version: Boost 1.35.0 Severity: Problem
Keywords: Cc: Stefan Seefeld

Description

Extracting a C++ pointer from a python object which inherited two base classes form C++ doesn't work correctly. Only the first base can be extracted, the second cannot be extracted under all circumstances. The script runs within an embedded python interpreter.

CPP code:

#define BOOST_PYTHON_STATIC_LIB
#include <boost/python.hpp>

using namespace boost::python;

class Base1
{
public:
	virtual ~Base1() {}
};

class Base2
{
public:
	virtual ~Base2() {}
};


void extractBase1( boost::python::object& obj)
{
	Base1* ex1 = extract<Base1*>(obj) BOOST_EXTRACT_WORKAROUND;
	Base1& ex2 = extract<Base1&>(obj) BOOST_EXTRACT_WORKAROUND;
}

void extractBase2( boost::python::object& obj)
{
	Base2* ex1 = extract<Base2*>(obj) BOOST_EXTRACT_WORKAROUND;
	Base2& ex2 = extract<Base2&>(obj) BOOST_EXTRACT_WORKAROUND;
}


BOOST_PYTHON_MODULE(Test_ext)
{
	class_<Base1, boost::noncopyable>("Base1");
	class_<Base2, boost::noncopyable>("Base2");

	def("extractBase1", extractBase1);
	def("extractBase2", extractBase2);
}


int main(int, char **) 
{
	try
	{
		Py_Initialize();	  
		initTest_ext();

		object main = import("__main__");
		object dictionary(main.attr("__dict__"));
		object result = exec_file("test.py", dictionary, dictionary );
	}
	catch (error_already_set)
	{
		PyErr_Print();
	}

	return 0;
}

test.py:

from Test_ext import *

class both1(Base1, Base2):
	def foo(self):
		print "foo!"

class both2(Base2, Base1):
	def foo(self):
		print "foo2!"

obj1 = both1()

extractBase1(obj1)
extractBase2(obj1)	# doesn't work

obj2 = both()

extractBase1(obj2)	# doesn't work
extractBase2(obj2)

Change History (7)

comment:1 by Dave Abrahams, 14 years ago

Does this fail even when the dtors are non-virtual?

in reply to:  1 ; comment:2 by bloodyfanatic@…, 14 years ago

Replying to dave:

Does this fail even when the dtors are non-virtual?

yes it does, with the same error as always:

Traceback (most recent call last):
  File "test.py", line 15, in <module>
    extractBase2(obj1)  # doesn't work
TypeError: No registered converter was able to extract a C++ pointer to type class Base2 from this Python object of type both1

in reply to:  2 ; comment:3 by Dave Abrahams, 14 years ago

Cc: Stefan Seefeld added
Resolution: invalid
Status: newclosed

Replying to bloodyfanatic@gmx.de:

Replying to dave:

Does this fail even when the dtors are non-virtual?

yes it does, with the same error as always:

In that case, please post the fully reduced test case

Also, you need an init method in both1 and both2 that initializes *both* bases or this can never work, because a wrapped class' init function is what actually constructs the C++ base object. Without it, there is no base object to which a pointer can be returned from extract:

  def __init__(self):
     Base1.__init__(self)
     Base2.__init__(self)

I don't know how to do this properly with super (the technique Stefan was suggesting), but his suggestion as written didn't take care of both bases, as the following demonstrates:

>>> class X(object):
...    def __init__(self):
...        print 'X'
... 
>>> class Y(object):
...     def __init__(self):
...         print 'Y'
... 
>>> class Z(X,Y): pass  # essentially what you posted
... 
>>> z = Z()
X
>>> class Z(X,Y):
...     def __init__(self):
...         super(Z,self).__init__()  # Stefan's suggestion
... 
>>> z = Z()
X
>>> class Z(X,Y):
...     def __init__(self):
...         X.__init__(self)
...         Y.__init__(self)
...
>>> z = Z()
X
Y
>>>

I'm pretty sure this solves your problem so I'm closing the ticket, but if it doesn't, please re-open it.

in reply to:  3 comment:4 by anonymous, 14 years ago

Replying to dave:

Replying to bloodyfanatic@gmx.de:

Replying to dave:

Does this fail even when the dtors are non-virtual?

yes it does, with the same error as always:

In that case, please post the fully reduced test case

Also, you need an init method in both1 and both2 that initializes *both* bases or this can never work, because a wrapped class' init function is what actually constructs the C++ base object. Without it, there is no base object to which a pointer can be returned from extract:

  def __init__(self):
     Base1.__init__(self)
     Base2.__init__(self)

I don't know how to do this properly with super (the technique Stefan was suggesting), but his suggestion as written didn't take care of both bases, as the following demonstrates:

>>> class X(object):
...    def __init__(self):
...        print 'X'
... 
>>> class Y(object):
...     def __init__(self):
...         print 'Y'
... 
>>> class Z(X,Y): pass  # essentially what you posted
... 
>>> z = Z()
X
>>> class Z(X,Y):
...     def __init__(self):
...         super(Z,self).__init__()  # Stefan's suggestion
... 
>>> z = Z()
X
>>> class Z(X,Y):
...     def __init__(self):
...         X.__init__(self)
...         Y.__init__(self)
...
>>> z = Z()
X
Y
>>>

I'm pretty sure this solves your problem so I'm closing the ticket, but if it doesn't, please re-open it.

just tested that and it works. Thank you very much :)

comment:5 by Stefan Seefeld, 14 years ago

Dave,

The use of 'super' with init does in fact work, as long as all classes use it:

class A(object):
    def __init__(self):
        super(A, self).__init__()
        print 'A'

class B(object):
    def __init__(self):
        super(B, self).__init__()
        print 'B'

class C(A, B):
    def __init__(self):
        super(C, self).__init__()
        print 'C'

c = C() # prints A B C

The reason for this is pretty well explained in http://fuhm.net/super-harmful/. In a nutshell: super() doesn't (necessarily) invoke the superclass method itself, but the next one in the MRO chain. If you omit super in one case, you break out of this chain.

FWIW.

comment:6 by Dave Abrahams, 14 years ago

I knew that once ;-) thanks for the reminder.

comment:7 by (none), 14 years ago

Milestone: Boost 1.35.1

Milestone Boost 1.35.1 deleted

Note: See TracTickets for help on using tickets.