I have a class as follows:
self.foo = "foo"
self.bar = "bar"
self.methodCalls = 0 #tracks number of times any function in the instance is run
self.methodCalls += 1
No, there are no hooks on a class to do this. Methods are attributes too, albeit somewhat special in that they are produced when accessing the function object on the instance; functions are descriptors.
The call to a method object is then a separate step from producing the method object:
>>> class Foo(object): ... def bar(self): ... return 'bar method on Foo' ... >>> f = Foo() >>> f.bar <bound method Foo.bar of <__main__.Foo object at 0x100777bd0>> >>> f.bar is f.bar False >>> stored = f.bar >>> stored() 'bar method on Foo'
It is the task of the
object.__getattribute__() method to invoke the descriptor protocol, so you could hook into that to see when a method is produced, but you'd still need to wrap that produced method object to detect calls. You could return an object with a
__call__ method that proxies for the actual method for example.
However, it'd be easier to decorate each method with a decorator that increments a counter every time it is called. Take into account decorators apply to a function before it is bound, so you'll have to pass
from functools import wraps def method_counter(func): @wraps(func) def wrapper(self, *args, **kwargs): self.methodCalls += 1 return func(self, *args, **kwargs) return wrapper
You'd still need to apply this to all functions in your class. You could apply this manually to all methods you want to count:
class MyClass(object): def __init__(self): self.foo = "foo" self.bar = "bar" self.methodCalls = 0 #tracks number of times any function method is run @method_counter def get_foo(self): return self.foo @method_counter def get_bar(self): return self.bar
or you could use a metaclass:
import types class MethodCounterMeta(type): def __new__(mcls, name, bases, body): # create new class object for name, obj in body.items(): if name[:2] == name[-2:] == '__': # skip special method names like __init__ continue if isinstance(obj, types.FunctionType): # decorate all functions body[name] = method_counter(obj) return super(MethodCounterMeta, mcls).__new__(mcls, name, bases, body) def __call__(cls, *args, **kwargs): # create a new instance for this class # add in `methodCalls` attribute instance = super(MethodCounterMeta, cls).__call__(*args, **kwargs) instance.methodCalls = 0 return instance
This takes care of everything the decorator needs, setting a
methodCalls attribute for you, so your class doesn't have to:
class MyClass(object): __metaclass__ = MethodCounterMeta def __init__(self): self.foo = "foo" self.bar = "bar" def get_foo(self): return self.foo def get_bar(self): return self.bar
Demo of the latter approach:
>>> class MyClass(object): ... __metaclass__ = MethodCounterMeta ... def __init__(self): ... self.foo = "foo" ... self.bar = "bar" ... def get_foo(self): ... return self.foo ... def get_bar(self): ... return self.bar ... >>> instance = MyClass() >>> instance.get_foo() 'foo' >>> instance.get_bar() 'bar' >>> instance.methodCalls 2
The above metaclass only considers function objects (so the result of
def statements and
lambda expressions) part of the class body for decoration. It ignores any other callable objects (there are more types that have a
__call__ method, such as
functools.partial objects), as are functions added to the class later on.