How does "delegation" work as per the FAQ?
wtrenker at shaw.ca
Thu Dec 25 15:40:53 CET 2003
Rene Pijlman wrote:
> I don't understand how __getattr__ accomplishes delegation of other
> methods. I'd expect __getattr__ to be called for attribute access, not for
> method calls.
In Python a method is a callable attribute. Since a method is also an attribute, __getattr__ works equally well with instance method attributes as with instance data attributes.
This can be very confusing for programmers coming from a statically-typed background. Try to keep in mind that Python is dynamically-typed. So in the case of attributes and methods, python doesn't care what an attribute represents until the last moment, at run-time, when the program attempts to call the attribute. It is only then that Python checks to see if the attribute is callable and treats it as a method. This is real paradigm shifting material for some of us. But once you get enough examples under your belt and the gears suddenly shift, the flexibility and elegance of Python jumps out at you.
> And how would the arguments of a method call be passed on?
First a quick review, by example:
>>> class A:
... def m(self, msg): print msg
>>> a = A()
<bound method A.m of <__main__.A instance at 0x4026a5ec>>
>>> a.m('hello world')
When python looks-up attribute m in object a it returns the object bound to the attribute. At that point, a.m could represent any object: an integer, a list, a callable, etc.. Python will only attempt to call a.m when, at run-time, it comes across the () as in a.m('hello world'). The point here is that the look-up of the method's name is a completely separate step from calling it. The lookup (done internally by python in the above example) is done with getattr which only returns an object, it doesn't call it. So getattr is not involved with things such as function arguments or return values.
Building on the above snippet:
>>> class B:
... a = A()
... def __getattr__(self,name):
... return getattr(self.a,name)
>>> b.m('hello universe')
The statement b=B() creates an instance of class B and binds the instance object to variable b. That instance will have an attribute a which is an instance object of class A.
The statement b.m('hello universe') first causes the attribute named m to be looked-up in object b. Class B doesn't define an attribute named m, but class B does have a __getattr__ attribute (method) so python calls it. The __getattr__ method uses the getattr function to lookup what boils down to attribute m in b.a. Since b's attribute a is an instance of class A which does have an attribute named m, the getattr is satisfied. The net effect is that the expression b.m resolves to the method object b.a.m.
So after all that, back in statement b.m('hello universe'), python sees the ('hello universe') after the b.m, verifies that the result of looking-up b.m is a callable object, and calls it with the enclosed argument. The reason the __getattr__ method's code doesn't need to deal with argument lists is that the call never happens there. In the example, it happens on the command line at statement b.m('hello universe'). In that statement, the __getattr__ method's code only involvement is with determining the object for the expression b.m.
I don't know if this attempted explanation helps or just adds to the confusion. I know how obvious these concepts are once you've grasped them. But I also know how frustrating it can be until you reach that sudden "aha! moment" and you realize you've got it.
More information about the Python-list