0

I am trying to log all method calls for an object. By overriding __getattribute__, I can intercept these calls and print them. My actual object has dozens of methods. To simplify, let's look at a simpler object. Something like this:

class Classy:
    def do_it(self):
        print('We did it!')

    def __getattribute__(self, attr):
        print(f'Calling {attr}')
        return Classy.__getattribute__(self, attr)


c = Classy()
c.do_it()

This code throws:

RecursionError: maximum recursion depth exceeded while calling a Python object

How can __getattribute__ look up a method in the child class while bypassing a call to itself?

EDIT: Interestingly, the call to __init__() bypassed __getattribute__. Instead, the error was triggered by the call to do_it(). That might be a clue.

EDIT2: This is a related question. But after studying the answers there, it still wasn't clear (to me at least) that calls to the parent's __getattribute__ method would in turn get the child class method.

ChaimG
  • 7,024
  • 4
  • 38
  • 46
  • You may wish to look at this https://stackoverflow.com/questions/371753/how-do-i-implement-getattribute-without-an-infinite-recursion-error/371833#371833 –  Jul 27 '21 at 16:18
  • By the way, you're not talking about class methods here. `do_it` is an example of an *instance method*. Class methods are a different thing in Python. – Silvio Mayolo Jul 27 '21 at 16:19
  • Does this answer your question? [How do I implement \_\_getattribute\_\_ without an infinite recursion error?](https://stackoverflow.com/questions/371753/how-do-i-implement-getattribute-without-an-infinite-recursion-error) – Tomerikoo Jul 27 '21 at 16:23
  • @AndyKnight If you think this question has an answer somewhere else in this site - [flag it as duplicate](https://stackoverflow.com/help/privileges/flag-posts) instead of posting a link as a comment... – Tomerikoo Jul 27 '21 at 16:23
  • I looked at the related question (and answers). However it wasn't clear to me from those answers that calls to super() would in turn find the child class' method. – ChaimG Jul 27 '21 at 18:09
  • The problem was never finding the child's method. You get infinite recursion *because* the child method is being called... over and over. It's the *parent* method you want to call to do the actual work of retrieving the attribute, in addition to whatever additional work your override is doing. – chepner Jul 27 '21 at 18:23
  • Yes, it was clear from those answers that calling` super().__getattribute__` would not throw an infinite recursion error. However, it was not clear from those answers that it would find a child method. See the Tomerikoo comment to your answer. – ChaimG Jul 27 '21 at 18:43

2 Answers2

2

You need to call the parent's __getattribute__ method, which is what would have been called had you not defined Classy.__getattribute__.

class Classy:
    def do_it(self):
        print('We did it!')

    def __getattribute__(self, attr):
        print(f'Calling {attr}')
        return super().__getattribute__(attr)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Interesting. The parent object's `__getattribute__` method finds `do_it()` in the child class. – ChaimG Jul 27 '21 at 16:19
  • @ChaimG `__getattribute__` is called from the parent class, yes, but the same `self` is passed so yes, it finds `do_it` – Tomerikoo Jul 27 '21 at 16:21
  • `object.__getattribute__` is really the only way to *get* an attribute value from an object. Overrides invariably either return some other value instead of trying to get an attribute value, or they just do some additional work before asking their parent to do it for them. – chepner Jul 27 '21 at 16:22
  • @tomerinko: I wonder what dictionary is looked up by the parent class, and why that dictionary's `__getattribute__` method is not overridden by the child's `__gettattribute__` method. – ChaimG Jul 27 '21 at 16:47
  • At some point, the "attribute is in some object's `__dict__`" way of thinking about it fails. Not all objects have a `__dict__` attribute, and clearly you would get into a situation of infinite recursion if `x.__getattribute__` itself required an attribute lookup. Eventually, you reach `object.__getattribute__`, and that just *works*, because the implementation says it does and it evaluates it for you. – chepner Jul 27 '21 at 17:02
1

You need to access the underlying object instead of your class:

class Classy:
    def do_it(self):
        print('We did it!')

    def __getattribute__(self, attr):
        print(f'Calling {attr}')
        return object.__getattribute__(self, attr)
tituszban
  • 4,797
  • 2
  • 19
  • 30
  • 1
    Hard-coding `object` is the same as using `super()` in this case, but restricts how well your class behaves in the face of multiple inheritance. Use `super` unless you have a really good reason not to. – chepner Jul 27 '21 at 16:23