C wrappers and the proxy dilemma
Ken Seehart
ken at seehart.com
Fri Jun 22 17:00:02 EDT 2007
Anyone who has wrapped C or C++ libraries has encountered the proxy
dilemma.
A long time ago, I naively thought that I could start by deriving my
high level python class from the c-python type, but this leads to many
difficult problems because several of the underlying methods return the
low level objects. After reading up on the subject, I learned that the
"correct" solution is to use composition rather than inheritance. It
makes sense, but it is nevertheless rather annoying.
An excellent example of this is the wxPython library, which uses
composition (proxies) and even solves the OOR (original object return)
problem that is associated with this kind of proxy oriented solution.
Conclusion: There is no really good solution to this kind of thing that
doesn't get messy. Wrapping C or C++ in python becomes difficult
whenever you have methods that return (or take as arguments) instances
of the objects that you are trying to wrap in high level python. Any
solution seems to add more overhead than is desirable (in terms of both
programmer time and run time).
This problem comes up over and over again, so perhaps it is even worth a
PEP if a change to the python language would facilitate a more
convenient solution to this nagging problem.
First, mentally set aside the idea of using composition. I understand
the set of problems that it solves, but it also creates a new set of
problems (such as function call overhead on every method call). I also
understand why composition is better than inheritance. But in order to
contemplate this proposal, it is necessary to temporarily set aside the
composition idea. This proposal involves taking another look at
something slightly more akin to the inheritance approach that we all
gave up on before.
Okay, so here is a somewhat radical proposal:
Add a writable __overload__ attribute to low level python type. This
would be assigned a python 'type' instance (or None to have no effect).
The effect is to place __overload__ at the /beginning /of the __mro__ of
the low level type, making it possible to directly alter the behavior of
the base python type without using inheritance. This means that
instances of the base type acquire the attributes of our high level
python class. It is important that the __overload__ type is checked
/before /the actual type.
Yeah, it sounds a bit scary at first. A bit too loose maybe. So the
type definition should have to explicitly enable this feature to prevent
people from doing bad things. But dwelling too much on the scariness
seems somewhat non-pythonic. It is more important that we can do really
good things than that we prevent ourselves from going out of our way to
do bad things (as long as it is not too /easy /to do bad things).
So here are some of the benefits:
1. Eliminates the duality between proxy objects and low level objects.
2. Increased speed on methods whose syntaxes are not being altered
(because no wrapper is needed).
3. Much less work than writing wrappers for every method (even if such a
task is partially automated).
Usage example:
from foolib import c_foo
class Foo(c_foo):
"""Overload of c_foo"""
def __new__(typ, *args, **kwargs):
return c_foo(*args, **kwargs)
def __init__(self, parrot_state):
c_foo.__init__(self)
self.parrot_state = parrot_state
def myfunc(self):
return 5 * self.juju(self.parrot_state) # juju method is defined
in foolib
# This makes all c_foo instances into Foo instances: Poof!
c_foo.c_footype.__overload__ = Foo
x = Foo('not dead yet') # actually creates a c_foo instance that looks
like a Foo instance
# c_foo.Add is defined in foolib and takes c_foo instances
x.Add(Foo('strange'))
x.Add(Foo('missing'))
# c_foo.GetChildren is defined in foolib and returns c_foo instances
for y in x.GetChildren():
print y.myfunc() # Wow, I am magically accessing Foo.myfunc
Yeah I know, that's an infinitely recursive inheritance; but that's
okay, we just implement the __overload__ feature such that the MRO
thingy will do the right thing for us. The semantics will be that Foo
is derived from c_foo, and all instances of c_foo behave as if the are
instances of Foo.
I used the term "radical" earlier, to describe this idea. What I meant
was that it seems to violate some basic Object Oriented principles.
However, when evaluating solutions I try not to apply any principles too
dogmatically. Theory is only relevant when it has /practical
/consequences. So I recommend not quoting theory without also defending
the relevance of the theory to the case in point. IMO, anything that
eliminates thousands of lines of boilerplate code is worth bending
theory a little.
Is it possible to get the same syntactic/semantic results without
changing python?
- Ken
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-list/attachments/20070622/d4b4b443/attachment.html>
More information about the Python-list
mailing list