Wrapping method calls with metaclasses

  • Thread starter Lawrence Oluyede
  • Start date
L

Lawrence Oluyede

I've never used metaclasses in real life before and while searching through
the online Cookbook I found this gorgeous example:

"Wrapping method calls (meta-class example)"
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/198078

What I want to do in my app is to log all method calls and seems that the
metaclass example above do this fine but it fails when it's needed to
instantiate the class I'm in in a method. So if I do:

class Test(object):
__metaclass__ = LogTheMethods
def foo(self):
return Test()

this generate a Runtime Error cause recursion never stops :(
Any hint to enhance the example?
 
A

Alex Martelli

Lawrence Oluyede said:
I've never used metaclasses in real life before and while searching through
the online Cookbook I found this gorgeous example:

"Wrapping method calls (meta-class example)"
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/198078

What I want to do in my app is to log all method calls and seems that the
metaclass example above do this fine but it fails when it's needed to
instantiate the class I'm in in a method. So if I do:

class Test(object):
__metaclass__ = LogTheMethods
def foo(self):
return Test()

this generate a Runtime Error cause recursion never stops :(
Any hint to enhance the example?

I can't reproduce the infinite recursion you observe, by merging said
recipe and your definition of class Test, w/Python 2.4.2 on Mac OS 10.4.

Can you please supply a minimal complete example, e.g. simplifying
function logthemethod to just emit s/thing to stdout etc, that does
exhibit the runaway recursion problem you observe?


Alex
 
L

Lawrence Oluyede

Il 2005-12-07 said:
I can't reproduce the infinite recursion you observe, by merging said
recipe and your definition of class Test, w/Python 2.4.2 on Mac OS 10.4.

It seemed that the problem arose because I was not using super() in a new
style class based hierarchy.

class Test(object):
__metaclass__ = LogTheMethods
logMatch = '.*'

def __init__(self, foo):
self.foo = foo

def test(self):
return "test"

class TestChild(Test):
def __init__(self):
# with super this works
Test.__init__(self, "foo")

def test(self):
return "child"

d = {'test': Test,
'child': TestChild}

class MainTest(object):
__metaclass__ = LogTheMethods
logMatch = '.*'

def create_test(self, key):
return d[key]()

if __name__ == '__main__':
l = MainTest()
print l.create_test("child").test()


look in TestChild's __init__(). Not using super() fails with a

"""
File "/home/rhymes/downloads/simple_logger.py", line 55, in __init__
Test.__init__(self, "foo")
File "/home/rhymes/downloads/simple_logger.py", line 24, in _method
returnval = getattr(self,'_H_%s' % methodname)(*argl,**argd)
TypeError: __init__() takes exactly 1 argument (2 given)
"""

In my project I'm doing deeply nested recursion things and it exceeds the
maximum recursion limit for this problem this way.

I don't get to fully work the metaclass anyway for other weird reasons
(using super() I lose an attribute around?!) I'm gonna fix this.
 
L

Lawrence Oluyede

Il 2005-12-07 said:
I don't get to fully work the metaclass anyway for other weird reasons
(using super() I lose an attribute around?!) I'm gonna fix this.

It was a bug of one of the super() in the chain.
Still suffer from deep recursion limit error with that metaclass.
Keep trying.
 
P

Peter Otten

Lawrence said:
look in TestChild's __init__(). Not using super() fails with a

"""
File "/home/rhymes/downloads/simple_logger.py", line 55, in __init__
Test.__init__(self, "foo")
File "/home/rhymes/downloads/simple_logger.py", line 24, in _method
returnval = getattr(self,'_H_%s' % methodname)(*argl,**argd)
TypeError: __init__() takes exactly 1 argument (2 given)
"""

The problem with the recipe seems to be that the mangled _H_XXX() method is
always looked up in the child class. One fix may be to store the original
method in the wrapper instead of the class:

def logmethod(methodname, method):
def _method(self,*argl,**argd):
global indent

#parse the arguments and create a string representation
args = []
for item in argl:
args.append('%s' % str(item))
for key,item in argd.items():
args.append('%s=%s' % (key,str(item)))
argstr = ','.join(args)
print >> log,"%s%s.%s(%s) " %
(indStr*indent,str(self),methodname,argstr)
indent += 1
# do the actual method call
returnval = method(self, *argl,**argd)
indent -= 1
print >> log,'%s:'% (indStr*indent), str(returnval)
return returnval

return _method


class LogTheMethods(type):
def __new__(cls,classname,bases,classdict):
logmatch = re.compile(classdict.get('logMatch','.*'))

for attr,item in classdict.items():
if callable(item) and logmatch.match(attr):
classdict[attr] = logmethod(attr, item) # replace method by
wrapper

return type.__new__(cls,classname,bases,classdict)

Be warned that I did not thoroughly test that.

Peter
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,149
Members
46,695
Latest member
StanleyDri

Latest Threads

Top