run-time construction of methods

Lenard Lindstrom len-1 at telus.net
Tue Apr 6 20:05:04 EDT 2004


"Carlo v. Dango" <oest at soetu.eu> writes:

> Hello all.
> 
> I am in the need of wrapping certain objects at run-time. My initial
> approach was:
> 
> import inspect, new
> def make_wrapper(obj, methodName):
>      cls = obj.__class__
>      wrapmth = getattr(obj, methodName)
>      print "************ ", methodName
>      def wrapper(self, *args, **kwargs):
>          print "**before ", methodName
>          return wrapmth(*args, **kwargs)
>      return wrapper
> 
> class Person(object):
>      def __init__(self, age):
>          super(Person, self).__init__()
>          self.age = age
> 
>      def getAge(self):
>          return self.age
> 
>      def setAge(self, newage):
>          """sets the age of the person"""
>          self.age = newage
> 
> p = Person(33)
> setattr(p, "setAge", new.instancemethod(make_wrapper(p,"setAge"), p,
> p.__class__))
> p.setAge(22)
> print "age is ", p.getAge()
>
I like the approach of using a bound method to p to create a
new instance specific method for p.

>... 
> def make_wrapper(obj, methodName):
>      cls = obj.__class__
>      wrapmth = getattr(obj, methodName)
>      print "************ ", methodName
>      wrapperstr = """def wrapper(self, *args, **kwargs):
>          print "**before ", methodName
>          return wrapmth(*args, **kwargs)"""
>      exec(wrapperstr, globals(), locals())
>      return wrapper
> 
> but I get the error
> 
> NameError: global name 'methodName' is not defined
>
exec does not create a closure within the function calling it.
That is its downside. So everything is either local to wrapmth,
eg. parameters and variables created within the function, or
global. Fortunately you can still create a closure with exec
by declaring a function within a function. Here's my rewrite.
I hope it works for you.

def make_wrapper(obj, methodName):
    cls = obj.__class__
    wrapmth = getattr(obj, methodName)
    print "********** ", methodName
    wrapperstr = """\
def _closure(_wrapmth):
    def wrapper(self, *args, **kwds):
        "I wrap method %(methodName)s"
        print "**before %(methodName)s"
        return _wrapmth(*args, **kwds)
    return wrapper
_wrapper = _closure(wrapmth)
""" % {'methodName': methodName} # Insert strings directly into code
    locs = {'wrapmth': wrapmth} # Keep namespace clean to avoid conflicts
    exec(wrapperstr, {}, locs)
    return locs['_wrapper'] # and here is the result

See how the argument to _closure is the 'local' you want to keep
around. Also I don't use globals() or locals() directly since
they are cluttered. When you start trying to make the parameters
of wrapper meaningful you will want to minimize the chance
the parameter names conflict with other names within the scope
of the exec. That is also why _closure and _wrapmth begin
with underscores. You might want to choose even more cryptic names.
Also, inlining strings such as methodName instead of passing them
as variables into the exec scope reduces the chance of conflict.

Lenard Lindstrom
<len-l at telus.net>



More information about the Python-list mailing list