[Tutor] Counting method calls
Kent Johnson
kent37 at tds.net
Sat Sep 22 14:34:33 CEST 2007
Ricardo Aráoz wrote:
> Hi, I'm trying to count method calls. Tried this but...
>>>>> class MyList(list):
>> ... def __init__(self):
>> ... self.calls = 0
should call list.__init__(self) here.
>> ... def __getattr__(self, name):
>> ... self.calls += 1
>> ... return list.__getattribute__(self, name)
>>
>>>>> a = MyList()
>>>>> a
>> []
>>>>> a.append(1)
>>>>> a
>> [1]
>>>>> a.calls
>> 88
>>>>> a.append(3)
>>>>> a.calls
>> 88
>>>>> a.sort()
>>>>> a
>> [1, 3]
>>>>> a.calls
>> 176
>
> It's doing some strange things with self.calls.
What version of Python are you using? When I try this program it prints
0
0
0
Note that __getattr__() is only called when normal attribute access
*fails*, so I would not expect this to work.
> I've also tried :
>
>>>>> class MyList(list):
>> ... def __init__(self):
>> ... self.calls = 0
>> ... def __getattribute__(self, name): # Here's the change
>> ... self.calls += 1
>> ... return list.__getattribute__(self, name)
>>
>>>>> a = MyList()
>>>>> a
>> []
>>>>> a.append(1)
> File "<input>", line 5, in __getattribute__
> File "<input>", line 5, in __getattribute__
> .... snipped .....
> File "<input>", line 5, in __getattribute__
> File "<input>", line 5, in __getattribute__
> RuntimeError: maximum recursion depth exceeded
>
> Any idea what's going on in both tries? And how can I intercept method
> calls without defining all of list's methods.
The problem here is that __getattribute__() is called for *all*
attribute access including getting the value of self.calls to increment
it. So __getattribute__() calls itself without end which is the recipe
for a stack overflow.
If you change getattribute() to this it is closer to what you want:
def __getattribute__(self, name):
self.calls = list.__getattribute__(self, 'calls') + 1
return list.__getattribute__(self, name)
For me this prints
2
4
6
with your sequence of operations.
More problems - this counts *any* attribute access, not just callables.
You could change it to get the attribute and only count it if
callable(value) is true. But it also counts calls to implementation
methods which is probably not what you want - if list.sort() calls three
other methods, do you want a count of 4 for a call to sort()? And it
counts failed attribute access; that is easy to fix by incrementing
calls after the call to list.__getattribute__().
A different approach is to use delegation rather than inheritance to
access the list functions. Write __getattr__() to delegate to a list
attribute:
class MyList(object):
def __init__(self):
self._list = list()
self.calls = 0
def __getattr__(self, name):
value = getattr(self._list, name)
if callable(value):
self.calls += 1
return value
I think this does what you want. Notice that it doesn't have anything
special to do with lists, either, except instantiating a list. It can be
turned into a general-purpose counting wrapper by passing the instance
to be counted to the constructor:
class CallCounter(object):
def __init__(self, delegate):
self._delegate = delegate
self.calls = 0
def __getattr__(self, name):
value = getattr(self._delegate, name)
if callable(value):
self.calls += 1
return value
a = CallCounter(list())
etc.
Kent
More information about the Tutor
mailing list