[FRPythoneers] Method trickery

Jeffery D. Collins jcollins at boulder.net
Tue Oct 23 09:22:26 MDT 2001

On Mon, Oct 22, 2001 at 11:19:32PM -0600, Eric Brunson wrote:
> I've got a problem that I've decided to solve through brute force, but
> I spent quite a while trying to find the feature I wanted and now I'm
> just on a mission.
> Here's the situation:
> When I first started using python I wrote a very straight forward
> class for html generation that mimicked the behavior of the perl
> CGI.pm module.  Most of the methods of this class take a dict as the
> first argument and in all but a few cases, simply drop the key/value
> pairs into the html as text, e.g:
> Html.textfield( { 'name': 'myfield', 'width': 12, 'value': 'bob' } )
> simply returns the string:
> <INPUT width="12"  TYPE="text"  name="myfield"  value="bob" >
> Some methods take additional arguements, but by convention they all
> expect a dict as the first parameter.
> A while back, after learning more about python, I rewrote the class so
> that it accepted a variable named parameter list so the above would
> now be called: 
> Html.textfield( name='myfield', width=12, value='bob' )
> which I just like much more.
> The problem came when I started modifying all my code to use the new
> calling conventions, there was just too much of it for me to justify
> the time it was taking to convert over.  Remember that many of the
> methods take additional arguments and named parameter have to be after
> all positional arguments.
> Be that as it may, whether I'm just lazy or whatever, it occurred to
> me that there might be a mechanism similar to the __getitem__ or
> __getattr__ methods that I could use to intercept any method call and
> use the dict passed in to call the new code with the new conventions.
> Here's a kind of meta-python version of what I thought it'd look like:
> class Html( newHtml ):
>     def __methodcalled__( self, methodname, *args ):
> 	funcptr = getattr( self, methodname ) 
> 	if type( args[0] ) is type( {} ):
> 	    return apply( funcptr, args[1:], args[0] )
> 	else:
> 	    return apply( funcptr, args )

Here is a possible solution to your problem, though there are
undoubtedly better, more elegant ones.  Do use it, you have to change
the name of each old-style method, but only make the changes in the
class definition itself.  In the code below, the old methods are
prefixed with the string "old_".  Apparently, this is necessary
because getattr() will find an existing func in the class instance
rather than invoking the __getattr__ method to find it.  Changing the
method name tickles __getattr__ to be invoked.  The code also makes
use of nested scopes and was tested using Python-2.1.

As you upgrade your methods, simply remove the "old_" prefix and the
method will avoid the wrapper altogether. 

You may have to fix up the code in wrapper to better suite your needs.

file: trick.py
from __future__ import nested_scopes

class Html:
    def __init__(self):
        self.x = 1
    def old_func(self, x):
        print "this is an old func"

    def __getattr__(self, attr):

        d = self.__class__.__dict__
        name = 'old_' + attr
        if d.has_key(name) and hasattr(d[name], 'func_code'):
            def wrapper(*args, **kw):
                funcptr = getattr( self, name )
                if type( args[0] ) is type( {} ):
                    return apply( funcptr, args[1:], args[0] )
                    return apply( funcptr, args, kw)

            return wrapper
        elif self.__dict__.has_key(attr):
            return self.__dict__[attr]
            raise AttributeError, "Attribute not found: " + attr

import trick

h = trick.Html(); s = h.func(1)

> That be the entire amount of coding I would have to do to implement
> the sort of backwards compatability I want, but there is no
> __methodcall__ method of a class (you can see how it could be easy to
> get into an infinite recursion).
> I thought I had it sussed earlier with __getattr__, but when I
> actually started writing the code, I realized I could intercept the
> lookup of the method, but not the call.
> I also started reading about the "Don Beaudry hook" in "Metaclasses in
> Python 1.5" ( http://www.python.org/doc/essays/metaclasses ), but I
> think I'm going to have to read that a few dozen more times before I
> understand it well enough to know if that's the solution.
> So, any thoughts on a clever solution?
> e.
> BTW, I've actually written a wrapper for every method in newHtml that
> would need its arguments munged from the old calling conventions, so
> this isn't a solution to that problem, it's more of an academic
> exercise that has caught my fancy and I'd like figure out for future
> reference.

Jeffery Collins (http://www.boulder.net/~jcollins)

More information about the FRPythoneers mailing list