3

I am trying to implement a class in which an attempt to access any attributes that do not exist in the current class or any of its ancestors will attempt to access those attributes from a member. Below is a trivial version of what I am trying to do.

class Foo:
    def __init__(self, value):
        self._value = value

    def __getattr__(self, name):
        return getattr(self._value, name)

if __name__ == '__main__':
    print(Foo(5) > Foo(4)) # should do 5 > 4 (or (5).__gt__(4))

However, this raises a TypeError. Even using the operator module's attrgetter class does the same thing. I was taking a look at the documentation regarding customizing attribute access, but I didn't find it an easy read. How can I get around this?

Tyler Crompton
  • 12,284
  • 14
  • 65
  • 94
  • 1
    Thanks for the good, simple example--but what's the point of `Bar` there? Your question's no different without it. – ron rothman Sep 07 '12 at 03:37
  • @ron.rothman, *shrugs shoulders* That's the basic gist of what my code is doing. I simplified it even further. :) – Tyler Crompton Sep 07 '12 at 03:39
  • Also, attributes are strings, not ints. Your use of `Foo(5)` indicates that maybe you're conflating the two. – ron rothman Sep 07 '12 at 03:39
  • I'm confused. `__getattr__` will never be called here, because you're not ever getting any attribute. Do you simply want Foo(5) to behave as if it were `5` regarding comparisons? – DSM Sep 07 '12 at 03:39
  • @DSM I figured it would call `int`'s `__gt__()` method. But yes. The problem is that I don't know what all attributes will be needed, so I can't just hard code all of them. – Tyler Crompton Sep 07 '12 at 03:42
  • 1
    @DSM -- I guess the question is "Why doesn't `__getattr__` ever get called?" Why is method lookup different than attribute lookup? I'm pretty sure you're right, but I can't find a concrete reason in the docs... – mgilson Sep 07 '12 at 03:42
  • @TylerCrompton -- I wasn't actually criticizing the title you gave to this post. I was actually asking that question myself ;). In any event, I approve the change. – mgilson Sep 07 '12 at 04:56

2 Answers2

7

If I understand you correctly, what you are doing is correct, but it still won't work for what you're trying to use it for. The reason is that implicit magic-method lookup does not use __getattr__ (or __getattribute__ or any other such thing). The methods have to actually explicitly be there with their magic names. Your approach will work for normal attributes, but not magic methods. (Note that if you do Foo(5).__lt__(4) explicitly, it will work; it's only the implicit "magic" lookup --- e.g., calling __lt__ when < is used) --- that is blocked.)

This post describes an approach for autogenerating magic methods using a metaclass. If you only need certain methods, you can just define them on the class manually.

Community
  • 1
  • 1
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
1

__*__ methods will not work unless they actually exist - so neither __getattr__ nor __getattribute__ will allow you to proxy those calls. You must create every single methods manually.

Yes, this does involve quite a bit of copy&paste. And yes, it's perfectly fine in this case.

You might be able to use the werkzeug LocalProxy class as a base or instead of your own class; your code would look like this when using LocalProxy:

print(LocalProxy(lambda: 5) > LocalProxy(lambda: 4))
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636