design question - python extensions in C++
Greetings, I would like to get some feedback regarding some design decisions on some work I have done on C++ extensions. The design philosopy in CXX_Extensions by P. Dubois is to create a unique type object for each C++ class. This is basically the approach used in CPython, where each python type has an associated type object. These type objects are created through the use of a templated class in which the class being defined is given as an argument to the template class. In my use of CXX, I found myself writing very similar code for each class I derived. For example, each class needed a getattr function that was identical to all the other getattr functions in every class. This can partially be addressed by creating an intermediate class that provides a default implementation of some of these common functions. In several posts in December, 1999, John Skaller proposed and outlined in detail an approach in which there is a single python type object that parallels a single C++ abstract base class. Since fools rush in where angels fear to tread, I have implemented some of the ideas outlined in J. Skaller's posts. The basic idea of the code is as follows: class CppPyObject: public PyObject { public: virtual int print() {throw TypeError("unimplemented_method");} } class CppPyType { // code to initialize static _typeobject struct PyTypeObject * type_object(); public: static bool check(PyObject * pyob) { return pyob && pyob->ob_type==type_object(); } static int print(PyObject* pyob) { try { if(check(pyob)) { static_cast<CppPyObject*>(pyob)->print(); return 0; } // error handling & return } } CppPyType& cpp_type() { // singleton pattern static ObjectType type; return type; } class MyObject: public CppPyObject { private: MyObject() { ob_refcnt = 1; ob_type=cpp_type().type_object(); } public: PyObject* create(PyObject*, PyObject*); virtual int print() {cout << "It's alive";} } This approach seems to work, and reduces the amount of work required to implement the virtual functions, since Python-specific stuff can be moved into the default function, including error checking. As discussed by Skaller, methods can be added by overloading the virtual getattr function in the concrete class. Taking Skaller's hint, I used stl::map<> to implement the method tables. There are several design decisions now that I would like to get feedback on. One problem I'm running into now is how to safely convert PyObject*'s to the appropriate and specific C++ implementation. In the CXX approach, there would be a static function MyObject::check. Again, this is similar to the CPython approach, which checks to see if the address of the type object since one-to-one correspondence with the type object. In the Skaller approach, the type-checking routine has to be (can be?) implemented within the C++ iterface class CppPyObject. The design I am attempting is to use class-specific identifiers (static strings) in creation-type classes and the use a virtual "check" function to perform type checking using string comparison. For example, PyObject* function(PyObject *self, PyObject* args) { Tuple a(args); // check length PyObject* arg1 = a[0].ptr(); ObjectProtocol * arg1 = static_cast<ObjectProtocol*>(arg1); if(arg1->check("MyObject")) { MyObject * myob = static_cast<MyObject*>(arg1); // do useful stuff } else throw TypeError("Function requires MyObject argument"); } Does anyone have a better idea or approach that I should consider. A second problem is how to check for to see if a PyObject* implements a particular Python/C++ interface. It would be nice to write C++ functions that did not require conversion to a specific creation-type but instead could check to see if a creation-type implemented a particular interface, and then cast the PyObject* to a pointer to the interface. Python checks for it's interface "types" (number, sequence, mapping) by checking for a null-pointer in the type object. A C++ equivalent is needed but I don't really see how to do it. Anyway, I appreciate any feedback on these or other issues. My e-mail address at work is venner.brad@epa.gov, at home is vennerbc@mindspring.com.
David Abrahams and Barry Scott have also done quite a lot of improvement in this area. I don't have time right now to get into this but you should compare notes with them. There is some concern about recent changes to the C++ standard that they can brief you on. As I said in my talk at IPC8 I am looking to pass CXX off to some new caretakers since my current work does not involve C++. In the new approach, you inherit from PyObject to make a new object type. I liked what I did on the CXX_Objects side but was not satisfied with the extension side, so I'm glad that others have taken up the call.
-----Original Message----- From: c++-sig-admin@python.org [mailto:c++-sig-admin@python.org]On Behalf Of Brad Venner Sent: Friday, February 08, 1980 12:14 PM To: c++-sig@python.org Subject: [C++-SIG] design question - python extensions in C++
Greetings,
I would like to get some feedback regarding some design decisions on some work I have done on C++ extensions.
The design philosopy in CXX_Extensions by P. Dubois is to create a unique type object for each C++ class. This is basically the approach used in CPython, where each python type has an associated type object. These type objects are created through the use of a templated class in which the class being defined is given as an argument to the template class. In my use of CXX, I found myself writing very similar code for each class I derived. For example, each class needed a getattr function that was identical to all the other getattr functions in every class. This can partially be addressed by creating an intermediate class that provides a default implementation of some of these common functions.
In several posts in December, 1999, John Skaller proposed and outlined in detail an approach in which there is a single python type object that parallels a single C++ abstract base class. Since fools rush in where angels fear to tread, I have implemented some of the ideas outlined in J. Skaller's posts. The basic idea of the code is as follows:
class CppPyObject: public PyObject { public: virtual int print() {throw TypeError("unimplemented_method");} } class CppPyType { // code to initialize static _typeobject struct PyTypeObject * type_object(); public: static bool check(PyObject * pyob) { return pyob && pyob->ob_type==type_object(); } static int print(PyObject* pyob) { try { if(check(pyob)) { static_cast<CppPyObject*>(pyob)->print(); return 0; } // error handling & return } } CppPyType& cpp_type() { // singleton pattern static ObjectType type; return type; } class MyObject: public CppPyObject { private: MyObject() { ob_refcnt = 1; ob_type=cpp_type().type_object(); } public: PyObject* create(PyObject*, PyObject*); virtual int print() {cout << "It's alive";} }
This approach seems to work, and reduces the amount of work required to implement the virtual functions, since Python-specific stuff can be moved into the default function, including error checking. As discussed by Skaller, methods can be added by overloading the virtual getattr function in the concrete class. Taking Skaller's hint, I used stl::map<> to implement the method tables.
There are several design decisions now that I would like to get feedback on. One problem I'm running into now is how to safely convert PyObject*'s to the appropriate and specific C++ implementation. In the CXX approach, there would be a static function MyObject::check. Again, this is similar to the CPython approach, which checks to see if the address of the type object since one-to-one correspondence with the type object. In the Skaller approach, the type-checking routine has to be (can be?) implemented within the C++ iterface class CppPyObject. The design I am attempting is to use class-specific identifiers (static strings) in creation-type classes and the use a virtual "check" function to perform type checking using string comparison. For example,
PyObject* function(PyObject *self, PyObject* args) { Tuple a(args); // check length PyObject* arg1 = a[0].ptr(); ObjectProtocol * arg1 = static_cast<ObjectProtocol*>(arg1); if(arg1->check("MyObject"))
MyObject * myob = static_cast<MyObject*>(arg1); // do useful stuff } else throw TypeError("Function requires MyObject argument"); }
Does anyone have a better idea or approach that I should consider.
A second problem is how to check for to see if a PyObject* implements a particular Python/C++ interface. It would be nice to write C++ functions that did not require conversion to a specific creation-type but instead could check to see if a creation-type implemented a particular interface, and then cast the PyObject* to a pointer to the interface. Python checks for it's interface "types" (number, sequence, mapping) by checking for a null-pointer in the type object. A C++ equivalent is needed but I don't really see how to do it.
Anyway, I appreciate any feedback on these or other issues. My e-mail address at work is venner.brad@epa.gov, at home is vennerbc@mindspring.com.
_______________________________________________ C++-SIG maillist - C++-SIG@python.org http://www.python.org/mailman/listinfo/c++-sig
Brad Venner wrote:
There are several design decisions now that I would like to get feedback on. One problem I'm running into now is how to safely convert PyObject*'s to the appropriate and specific C++ implementation.
Unfortunately, this is a necessary evil of object oriented programming -- covariant typing requires a dynamic cast.
PyObject* function(PyObject *self, PyObject* args) { Tuple a(args); // check length PyObject* arg1 = a[0].ptr(); ObjectProtocol * arg1 = static_cast<ObjectProtocol*>(arg1); if(arg1->check("MyObject")) { MyObject * myob = static_cast<MyObject*>(arg1); // do useful stuff } else throw TypeError("Function requires MyObject argument"); }
Does anyone have a better idea or approach that I should consider.
Why not just use a dyanamic cast? MyObject *o = dynamic_cast<MyObject *>(arg1); This should work because the MyObject is derived from PyObject.
A second problem is how to check for to see if a PyObject* implements a particular Python/C++ interface. It would be nice to write C++ functions that did not require conversion to a specific creation-type but instead could check to see if a creation-type implemented a particular interface, and then cast the PyObject* to a pointer to the interface. Python checks for it's interface "types" (number, sequence, mapping) by checking for a null-pointer in the type object. A C++ equivalent is needed but I don't really see how to do it.
This is tricky. One way is to further factor the abstraction PyObject by making abstract bases PySequence and PyMapping. Then (in theory) you can again use a dynamic cast: if (dynamic_cast<PySequence*>(object)) { .. a sequence .. } else { .. not a sequence .. } -- John (Max) Skaller, mailto:skaller@maxtal.com.au 10/1 Toxteth Rd Glebe NSW 2037 Australia voice: 61-2-9660-0850 homepage: http://www.maxtal.com.au/~skaller download: ftp://ftp.cs.usyd.edu/au/jskaller
participants (3)
-
Brad Venner -
Paul F. Dubois -
skaller