www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Polymorphism and wrapping Python

reply Kirk McDonald <kirklin.mcdonald gmail.com> writes:
I'm currently writing a family of classes that wrap the object layers of 
the Python/C API. The class hierarchy looks something like this:

DPyObject
   DPyNumber
     DPyInteger
     DPyFloat
     (etc)
   DPySequence
     DPyString
     DPyList
     (etc)
   DPyMapping
     DPyDict
   (etc)

All of the classes on the second level are declared as "abstract," and 
(appropriately) represent the Python/C API's abstract objects layer. 
DPyObject and the classes on the third level are not abstract and 
represent the concrete objects layer. (Though I think I may eventually 
make DPyObject abstract, too.)

Behind this is a factory function that takes a PyObject* (the Python/C 
API's usual way of representing a Python object) and returns a instance 
of the proper subclass of DPyObject.

I have a question, then, of polymorphism, and how closely I should try 
to emulate Python.

In Python, if you try to (for example) add two instances of a class that 
doesn't have the __add__ method (Python's equivalent of opAdd) defined, 
Python will raise a TypeError. If you try to do this in D, it is a 
compile-time error.

At the moment, DPyObject doesn't define opAdd. DPyNumber does, like this:

DPyNumber opAdd(DPyNumber rhs) {
     return make_dpyobject(PyNumber_Add(m_ptr, rhs.m_ptr));
}

(If you're unfamiliar with the Python/C API, PyNumber_Add takes two 
PyObject* arguments and returns a new PyObject* with the result. m_ptr 
is the only member of DPyObject, and is a PyObject*. make_dpyobject is 
my factory function that returns the proper subclass of DPyObject based 
on the type of the PyObject* passed to it.)

If we have:

DPyObject o = new DPyInteger(5);
DPyObject p = new DPyInteger(10);
DPyObject q = o + p; // Error! DPyObject doesn't define opAdd.

I'm trying to decide if this is a problem. I can easily define this for 
DPyObject:

DPyObject opAdd(DPyObject rhs) { assert(false); }

Then the above example would work, but I'm not totally sure that this is 
desirable behavior. In Python it certainly would be: that language is 
all about the duck typing. It would certainly simplify the API, in that 
all DPyObject-derived classes would have the same interface (now there's 
an idea, I should write an interface...). But I still have this 
irrational fear that it is a bad idea.

Anyone have more insight than I can currently muster?

-Kirk McDonald
Jun 03 2006
next sibling parent Daniel Keep <daniel.keep.lists gmail.com> writes:
Kirk McDonald wrote:
 I'm currently writing a family of classes that wrap the object layers of
 the Python/C API. The class hierarchy looks something like this:
 
 DPyObject
   DPyNumber
     DPyInteger
     DPyFloat
     (etc)
   DPySequence
     DPyString
     DPyList
     (etc)
   DPyMapping
     DPyDict
   (etc)
 
 All of the classes on the second level are declared as "abstract," and
 (appropriately) represent the Python/C API's abstract objects layer.
 DPyObject and the classes on the third level are not abstract and
 represent the concrete objects layer. (Though I think I may eventually
 make DPyObject abstract, too.)
 
 Behind this is a factory function that takes a PyObject* (the Python/C
 API's usual way of representing a Python object) and returns a instance
 of the proper subclass of DPyObject.
 
 I have a question, then, of polymorphism, and how closely I should try
 to emulate Python.
 
 In Python, if you try to (for example) add two instances of a class that
 doesn't have the __add__ method (Python's equivalent of opAdd) defined,
 Python will raise a TypeError. If you try to do this in D, it is a
 compile-time error.
 
 At the moment, DPyObject doesn't define opAdd. DPyNumber does, like this:
 
 DPyNumber opAdd(DPyNumber rhs) {
     return make_dpyobject(PyNumber_Add(m_ptr, rhs.m_ptr));
 }
 
 (If you're unfamiliar with the Python/C API, PyNumber_Add takes two
 PyObject* arguments and returns a new PyObject* with the result. m_ptr
 is the only member of DPyObject, and is a PyObject*. make_dpyobject is
 my factory function that returns the proper subclass of DPyObject based
 on the type of the PyObject* passed to it.)
 
 If we have:
 
 DPyObject o = new DPyInteger(5);
 DPyObject p = new DPyInteger(10);
 DPyObject q = o + p; // Error! DPyObject doesn't define opAdd.
 
 I'm trying to decide if this is a problem. I can easily define this for
 DPyObject:
 
 DPyObject opAdd(DPyObject rhs) { assert(false); }
 
 Then the above example would work, but I'm not totally sure that this is
 desirable behavior. In Python it certainly would be: that language is
 all about the duck typing. It would certainly simplify the API, in that
 all DPyObject-derived classes would have the same interface (now there's
 an idea, I should write an interface...). But I still have this
 irrational fear that it is a bad idea.
 
 Anyone have more insight than I can currently muster?
 
 -Kirk McDonald

If I remember correctly, there are a bunch of fields that may or may not actually be implemented for a particular PyObject. Things like __add__, __str__, __repr__, etc. I personally think that all of these should be on DPyObject, just with a default "blarg, I dies!" implementation. That way, the D binding operates in approximately the same fashion as Python itself. The only thing I think would be different is that if, say, __add__ fails, then Python tries __radd__. If I understand rightly, D does the same thing... except at compile time. Perhaps DPyObject should define a general-purpose opXXX that first tries the operand's __xxx__ method, then it's __rxxx__ method. In any case, good to know you've gotten so far :) -- Daniel -- Unlike Knuth, I have neither proven or tried the above; it may not even make sense. v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP http://hackerkey.com/
Jun 03 2006
prev sibling parent Hasan Aljudy <hasan.aljudy gmail.com> writes:
Kirk McDonald wrote:
 I'm currently writing a family of classes that wrap the object layers of 
 the Python/C API. The class hierarchy looks something like this:
 
 DPyObject
   DPyNumber
     DPyInteger
     DPyFloat
     (etc)
   DPySequence
     DPyString
     DPyList
     (etc)
   DPyMapping
     DPyDict
   (etc)
 
 All of the classes on the second level are declared as "abstract," and 
 (appropriately) represent the Python/C API's abstract objects layer. 
 DPyObject and the classes on the third level are not abstract and 
 represent the concrete objects layer. (Though I think I may eventually 
 make DPyObject abstract, too.)
 
 Behind this is a factory function that takes a PyObject* (the Python/C 
 API's usual way of representing a Python object) and returns a instance 
 of the proper subclass of DPyObject.
 
 I have a question, then, of polymorphism, and how closely I should try 
 to emulate Python.
 
 In Python, if you try to (for example) add two instances of a class that 
 doesn't have the __add__ method (Python's equivalent of opAdd) defined, 
 Python will raise a TypeError. If you try to do this in D, it is a 
 compile-time error.
 
 At the moment, DPyObject doesn't define opAdd. DPyNumber does, like this:
 
 DPyNumber opAdd(DPyNumber rhs) {
     return make_dpyobject(PyNumber_Add(m_ptr, rhs.m_ptr));
 }
 
 (If you're unfamiliar with the Python/C API, PyNumber_Add takes two 
 PyObject* arguments and returns a new PyObject* with the result. m_ptr 
 is the only member of DPyObject, and is a PyObject*. make_dpyobject is 
 my factory function that returns the proper subclass of DPyObject based 
 on the type of the PyObject* passed to it.)
 
 If we have:
 
 DPyObject o = new DPyInteger(5);
 DPyObject p = new DPyInteger(10);
 DPyObject q = o + p; // Error! DPyObject doesn't define opAdd.
 
 I'm trying to decide if this is a problem. I can easily define this for 
 DPyObject:
 
 DPyObject opAdd(DPyObject rhs) { assert(false); }
 
 Then the above example would work, but I'm not totally sure that this is 
 desirable behavior. In Python it certainly would be: that language is 
 all about the duck typing. It would certainly simplify the API, in that 
 all DPyObject-derived classes would have the same interface (now there's 
 an idea, I should write an interface...). But I still have this 
 irrational fear that it is a bad idea.
 
 Anyone have more insight than I can currently muster?
 
 -Kirk McDonald

I think one possibility is to use a d-to-d translator that inspects code and adds dummy functions to the DPyObject class as needed .. just like DPyObject function_that_may_not_exist___who_knows( DPyObject[] params ... ) { assert(false); } Then this translator would pass the "modified" code to the actual dmd compiler.
Jun 03 2006