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) ). _______________________________________________ Tutor maillist - Tutor@python.org http://mail.python.org/mailman/listinfo/tutor