[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