3

I'm working on a project right now that deals with functions in an abstract mathematical sense. Without boring the reader with the details, I'll say that I had the following structure in an earlier version:

class Foo(Bar):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.sub_unit = Foo(a, not b)

Of course, that's not quite the change I'm making to the arguments to Foo, but suffice to say, it is necessary that this property, if accessed repeatedly, result in an indefinitely long chain of Foo objects. Obviously, this results in an infinite recursion when one instantiates Foo. I solved this in the earlier version by removing the last line of init and adding the following to the Foo class:

def __getattr__(self, attr: str):
    if attr == 'sub_unit':
        return Foo(self.a, not self.b)
    else:
        return super().__getattr__(attr)

This worked quite well, as I could calculate the next object in the chain as needed.

In going over the code, though, I realize that for other reasons, I need an instance of Bar, not a sub-class of it. To see if I could override the getattr for a single instance, I tried the following:

>>> foo = Bar(a=1, b=2) # sub_unit doesn't get set here.
>>> foo.__getattr__ = lambda attr: 'foo'
>>> foo.a
1
>>> foo.__getattr__('a')
'foo'

What is happening here that I don't understand? Why isn't foo.a calling foo.__getattr__('a')?

Is there a good way to overwrite __getattr__ for a single instance, or is my best bet to re-factor all the code I have that reads sub_unit and friends to call those as functions, to handle this special case?

martineau
  • 119,623
  • 25
  • 170
  • 301
David Legg
  • 271
  • 1
  • 4
  • 12
  • 1
    See link in other comment - the reason you can't override it is because `__getattr__` is a special method – Harry Harrison Nov 19 '15 at 00:06
  • Also `__getattr__` is only invoked for *missing* attributes, i.e. those that aren't found the normal way. If you want to intercept access to *all* attributes, implement `__getattribute__`. – jonrsharpe Nov 19 '15 at 00:45
  • I wasn't fully aware of that subtlety with special methods, and that does seem to be the problem here. In response to your comment, jonrsharpe, I also tried to overwrite `__getattribute__` (though I neglected to mention so, my bad), to no effect. Also, if it's for missing arguments, why does it work in the class-based situation? In `init`, I set `sub_unit = None`. – David Legg Nov 19 '15 at 01:19
  • 1
    Perhaps you want `sub_unit` to be a `property`. Which returns `return Foo(self.a, not self.b)` when accessed. – Dan D. Nov 19 '15 at 05:03
  • @DanD. Properties look promising initially, but I can only seem to get them to work by including them in a class definition. Is there a good way to apply one to a single instance? – David Legg Nov 19 '15 at 10:19

1 Answers1

1

When you lookup the attribute a with foo.a, python looks it up in the instance's property dictionary. When it is not found, the __getattr__ method will then be called.

On the contrary, if a exists in the instance, __getattr__ will not be called.

sunhs
  • 391
  • 1
  • 6