[Tutor] Counting method calls

Ricardo Aráoz ricaraoz at gmail.com
Sat Sep 22 15:24:06 CEST 2007


Kent Johnson wrote:
> 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.

Foolish me.

> 
>>> ...     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

Py 0.9.5
Python 2.5 (r25:51908, Sep 19 2006, 09:52:17) [MSC v.1310 32 bit
(Intel)] on win32

I thought it might be you were trying the class with the list init call
but I tried it and works the same way.
Was using PyAlaMode, tried it using IDLE and it works like yours,
probably a bug of PyAlaMode.

> 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.

Yes, that was my conclusion too.

> 
> 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)
> 

Aaarghh! I was looking for the problem in the 'return' line and skipped
the 'self.calls' line. Thanks.

> 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())
> 

Yes, this would be exactly what I was looking for. As you can imagine
it's use was not really to count method calls but to add functionality
before or after the calls at will.

Sadly :
>>> a = CallCounter(list())
>>> a.append(1)
>>> a.calls
2
>>> a.append(2)
>>> a.append(3)
>>> a.calls
5
>>> a[3]

Traceback (most recent call last):
  File "<pyshell#15>", line 1, in <module>
    a[3]
TypeError: 'CallCounter' object is unindexable

>>> print a
<__main__.CallCounter object at 0x00C54170>


So my purpose of wrapping a class in an existing module in a transparent
manner is thwarted.
Any ideas? (yes, I know I should be doing my own thinking, I'll do it in
the afternoon after I've finished my chores :) )
BTW I've seen some recipes, one for adding a logger to a class which
could be adapted to my requirement, but they are way over my head and I
like to keep code simple and understandable (by me ;c) ).





More information about the Tutor mailing list