Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

interface boilerplate

33 views
Skip to first unread message

John Hunter

unread,
Oct 17, 2004, 3:40:25 PM10/17/04
to pytho...@python.org

In matplotlib, a plotting library with an OO API and a matlab-like
procedural interface, I have a lot of functions defined in the matlab
interface module that wrap similarly named class methods defined in
the Axes class of the axes module. Eg, the Axes class defines a plot
method and the matlab interface defines a plot function that gets the
current Axes instance, calls the plot method on that instance, and
does some error handling. Here is the matlab interface wrapper (gca()
returns the current Axes instance)

def plot(*args, **kwargs):
try:
ret = gca().plot(*args, **kwargs)
except ValueError, msg:
msg = raise_msg_to_str(msg)
error_msg(msg)
else:
draw_if_interactive()
return ret
plot.__doc__ = Axes.plot.__doc__

This is mostly boilerplate code that a lot of matlab interface
functions use, and I'd like to automatically generate it.

This appears to (mostly) work

def _wrap_axfunc(name):
def wrapper(*args, **kwargs):
try:
func = getattr(gca(), name)
ret = func(*args, **kwargs)
except ValueError, msg:
msg = raise_msg_to_str(msg)
error_msg(msg)
else:
draw_if_interactive()
return ret
wrapper.__doc__ = getattr(Axes, name).__doc__
#wrapper.__name__ = name
return wrapper

plot = _wrap_axfunc('plot')

The only problem I've seen so far is that the name of the function in
pydoc string is 'wrapper', and I want it to appear as "plot". I tried
setting the __name__ attribute, but it is read only.

Any suggestions on how to best define these matlab interface functions
by automatically wrapping the Axes instance functions? I'd like to
support python2.2 so python2.2 compliant solutions especially welcome.

JDH

Alex Martelli

unread,
Oct 18, 2004, 7:17:43 AM10/18/04
to
John Hunter <jdhu...@ace.bsd.uchicago.edu> wrote:
...

> The only problem I've seen so far is that the name of the function in
> pydoc string is 'wrapper', and I want it to appear as "plot". I tried
> setting the __name__ attribute, but it is read only.

Yes, a 2.3 problem, solved in 2.4.

> Any suggestions on how to best define these matlab interface functions
> by automatically wrapping the Axes instance functions? I'd like to
> support python2.2 so python2.2 compliant solutions especially welcome.

Then I guess upgrading to 2.4 is out of the question.

To make a function just like another but with a different name:

def changed_name_function(f, newname):
import new
return new.function(f.func_code, f.func_globals, newname,
f.func_defaults, f.func_closure)

I believe this should work in 2.2 as well (not tested).


Alex

John Hunter

unread,
Oct 18, 2004, 9:29:55 AM10/18/04
to Alex Martelli, pytho...@python.org
>>>>> "Alex" == Alex Martelli <ale...@yahoo.com> writes:

Alex> To make a function just like another but with a different
Alex> name:

Alex> def changed_name_function(f, newname): import new return
Alex> new.function(f.func_code, f.func_globals, newname,
Alex> f.func_defaults, f.func_closure)

Alex> I believe this should work in 2.2 as well (not tested).

I tested this - the signature of new.function in 2.2 is a bit
different

function(...)
Create a function object from (CODE, GLOBALS, [NAME [, ARGDEFS]]).

so it doesn't take the 5 arg version posted.

I am having a little trouble figuring out how to handle the call
signature for 2.2. I tried this modification (matplotlib._python23 is
a flag that returns True iff python version >=2.3


def changed_name_function(f, newname):
import new

if matplotlib._python23:
newf = new.function(f.func_code, f.func_globals, newname,
f.func_defaults, f.func_closure)
else:
if f.func_defaults is None:
argdefs = ()
else:
argdefs = f.func_defaults
newf = new.function(f.func_code, f.func_globals, newname,
argdefs)

newf.__doc__ = f.__doc__
return newf

I added the None check on f.func_defaults because I was getting the
error

TypeError: function() argument 4 must be tuple, not None

But this does not appear to be right either because I get a segfault
:-( Note that the suggestion works as advertised for python2.3.

Any ideas?

Thanks,
John Hunter

Alex Martelli

unread,
Oct 18, 2004, 10:57:43 AM10/18/04
to
John Hunter <jdhu...@ace.bsd.uchicago.edu> wrote:

> >>>>> "Alex" == Alex Martelli <ale...@yahoo.com> writes:
>
> Alex> To make a function just like another but with a different
> Alex> name:
>
> Alex> def changed_name_function(f, newname): import new return
> Alex> new.function(f.func_code, f.func_globals, newname,
> Alex> f.func_defaults, f.func_closure)
>
> Alex> I believe this should work in 2.2 as well (not tested).
>
> I tested this - the signature of new.function in 2.2 is a bit
> different
>
> function(...)
> Create a function object from (CODE, GLOBALS, [NAME [, ARGDEFS]]).
>
> so it doesn't take the 5 arg version posted.

Ah, it didn't take a closure. Could be quite a problem...

> I am having a little trouble figuring out how to handle the call
> signature for 2.2. I tried this modification (matplotlib._python23 is
> a flag that returns True iff python version >=2.3
>
>
> def changed_name_function(f, newname):
> import new
> if matplotlib._python23:
> newf = new.function(f.func_code, f.func_globals, newname,
> f.func_defaults, f.func_closure)
> else:
> if f.func_defaults is None:
> argdefs = ()
> else:
> argdefs = f.func_defaults
> newf = new.function(f.func_code, f.func_globals, newname,
> argdefs)
>
> newf.__doc__ = f.__doc__
> return newf
>
> I added the None check on f.func_defaults because I was getting the
> error
>
> TypeError: function() argument 4 must be tuple, not None
>
> But this does not appear to be right either because I get a segfault
> :-( Note that the suggestion works as advertised for python2.3.
>
> Any ideas?

Supporting old versions is never going to be easy -- I don't even have a
2.2 installation around to do such tests, any more. Perhaps for
versions < 2.3 you could simply degrade gracefully to perform no
renaming (and for versions >= 2.4 do the renaming the right way, by
assigning to f.func_name and returning f)... those who choose to stick
with 2.2 will just have to account that as one of the many limitations
and slow-downs their choice buys them...


Alex

John Hunter

unread,
Oct 18, 2004, 10:34:04 AM10/18/04
to Alex Martelli, pytho...@python.org
>>>>> "Alex" == Alex Martelli <ale...@yahoo.com> writes:

Alex> Supporting old versions is never going to be easy -- I don't
Alex> even have a 2.2 installation around to do such tests, any
Alex> more. Perhaps for versions < 2.3 you could simply degrade
Alex> gracefully to perform no renaming (and for versions >= 2.4
Alex> do the renaming the right way, by assigning to f.func_name
Alex> and returning f)... those who choose to stick with 2.2 will
Alex> just have to account that as one of the many limitations and
Alex> slow-downs their choice buys them...

It is a pain -- for a fair number of linux boxes, though, 2.2 is still
the default. Perhaps a better alternative for 2.2 is to simply fall back on
exec

__fmt = """\
def %(name)s(*args, **kwargs):
try:
ret = gca().%(name)s(*args, **kwargs)


except ValueError, msg:
msg = raise_msg_to_str(msg)
error_msg(msg)
else:
draw_if_interactive()
return ret

%(name)s.__doc__ = Axes.%(name)s.__doc__
"""

for name in _methods:
exec(__fmt%{'name':name})


JDH

Michael Spencer

unread,
Oct 18, 2004, 1:07:06 PM10/18/04
to pytho...@python.org
John Hunter wrote:
> In matplotlib, a plotting library with an OO API and a matlab-like
> procedural interface, I have a lot of functions defined in the matlab
> interface module that wrap similarly named class methods defined in
> the Axes class of the axes module. Eg, the Axes class defines a plot
> method and the matlab interface defines a plot function that gets the
> current Axes instance, calls the plot method on that instance, and
> does some error handling. Here is the matlab interface wrapper (gca()
> returns the current Axes instance)
>
> def plot(*args, **kwargs):
> try:
> ret = gca().plot(*args, **kwargs)

> except ValueError, msg:
> msg = raise_msg_to_str(msg)
> error_msg(msg)
> else:
> draw_if_interactive()
> return ret
> plot.__doc__ = Axes.plot.__doc__
>
> This is mostly boilerplate code that a lot of matlab interface
> functions use, and I'd like to automatically generate it.
>
> This appears to (mostly) work
>
> def _wrap_axfunc(name):
> def wrapper(*args, **kwargs):
> try:
> func = getattr(gca(), name)
> ret = func(*args, **kwargs)

> except ValueError, msg:
> msg = raise_msg_to_str(msg)
> error_msg(msg)
> else:
> draw_if_interactive()
> return ret
> wrapper.__doc__ = getattr(Axes, name).__doc__
> #wrapper.__name__ = name
> return wrapper
>
> plot = _wrap_axfunc('plot')
>
> The only problem I've seen so far is that the name of the function in
> pydoc string is 'wrapper', and I want it to appear as "plot". I tried
> setting the __name__ attribute, but it is read only.
>
> Any suggestions on how to best define these matlab interface functions
> by automatically wrapping the Axes instance functions? I'd like to
> support python2.2 so python2.2 compliant solutions especially welcome.
>
> JDH

John

The following descriptor-based delegation does not directly address your
question, but perhaps the idea is applicable. In any case, I'm curious
to get reactions to the approach. It may be well-known, but I haven't
seen a discussion of it in this group.

#Used on 2.3.3, but I think valid on 2.2+

class Delegate(object):
"""A descriptor that converts calls on obj, to calls on obj._data"""
def __init__(self, attr):
self.attr = attr
def __get__(self,obj,objtype = None):
return getattr(obj._data,self.attr) #Key step is here

#Example: wrap a subset of dict to get a read-only dict

class fdict(object):
"""Frozen Dictionary. Read only after init"""
def __init__(self,*args,**kw):
self._data = dict(*args,**kw)
def __repr__(self):
return "f"+repr(self._data)

"""Example - dict methods for read-only"""
fdictmethods = ['__cmp__', '__contains__', '__eq__', '__ge__',
'__getitem__','__gt__', '__iter__', '__le__', '__len__', '__lt__',
'__ne__','__reduce__','__reduce_ex__', '__str__', 'copy' , 'get',
'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues','keys','values']

"""Add each method as an auto-delegating descriptor"""
for attr in fdictmethods:
setattr(fdict,attr,Delegate(attr))


I like this because it's easy to automate, generates the the wrapper at
compile-time, and preserves the argspec of the wrapped functions

Michael

0 new messages