How does "delegation" work as per the FAQ?

B

Bill Trenker

Rene said:
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:
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:
.... a = A()
.... def __getattr__(self,name):
.... return getattr(self.a,name)
.... 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.

Regards,
Bill
 
R

Rene Pijlman

Section 6.5 "What is delegation?" of the FAQ says:

"Python programmers can easily implement delegation. For example, the
following class implements a class that behaves like a file but converts
all written data to uppercase:

class UpperOut:
def __init__(self, outfile):
self.__outfile = outfile
def write(self, s):
self.__outfile.write(s.upper())
def __getattr__(self, name):
return getattr(self.__outfile, name)

[...] All other methods are delegated to the underlying self.__outfile
object. The delegation is accomplished via the __getattr__ method; consult
the language reference for more information about controlling attribute
access."
http://www.python.org/doc/faq/programming.html#what-is-delegation

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. And how would the arguments of a method call be passed on?

The link to the language reference doesn't help much. It says about
__getattr__: "Called when an attribute lookup has not found the attribute
in the usual places". There is no mention of method calls.
 
V

Ville Vainio

__getattr__: "Called when an attribute lookup has not found the attribute
in the usual places". There is no mention of method calls.

A method is an attribute.
 
R

Rene Pijlman

Ville Vainio:
Rene Pijlman:

A method is an attribute.

I see. But how are the arguments of a method call passed on, when
delegation is implemented this way?
 
J

Jeff Epler

In Python, there's little difference between
f = o.m
f(3)
and
o.m(3)
either way, the attribute 'm' is looked up on o in exactly the same
way. So the delegation pattern works no matter whether 'o.m' is a
method or is data.

Jeff
 
M

Mike C. Fletcher

Rene said:
Ville Vainio:



I see. But how are the arguments of a method call passed on, when
delegation is implemented this way?
Jeff already gave you a fairly brief description of why this is so, but
I'll try to flesh it out somewhat. In Python, methods of objects are
actually themselves objects which wrap up an implementation function
(also an object) and a reference to the object on which they are to act
(the "self" parameter). To demonstrate this:
.... def y( self, value ):
.... print value
.... <method-wrapper object at 0x00ED3B78>

So, when you do item.y( somevalue ), you are doing this set of actions:

* doing an attribute lookup in the object item for the attribute name y
o in this example, this finds the unbound method x.y in the
class, which (via various hooks (see discussions of
"descriptors")) generates a new bound method object (item.y)
for the instance "item" and returns that.
o in the sample code you found, the methods for the file
object would not be found in the instance "item"'s
dictionary or class' dictionary, so the __getattr__ hook
(which is called when an attribute lookup fails) would be
called and accomplish the delegation by doing another
attribute lookup from the object to which it is delegating
responsibility.
* calling the __call__ method of the (newly created) bound method
object with the arguments (somevalue)
o in this example, the bound method has a reference to "item"
which it will pass as the first argument to it's underlying
"y" function, so the arguments passed to the function are
(item, somevalue) (corresponding to self, somevalue in the
method signature).
o In the sample code, the bound method would have a reference
to the delegated file, rather than the delegating object, so
it will act on the underlying file.

Enjoy yourself,
Mike

_______________________________________
Mike C. Fletcher
Designer, VR Plumber, Coder
http://members.rogers.com/mcfletch/
 
T

Terry Reedy

Rene Pijlman said:
Ville Vainio:

I see. But how are the arguments of a method call passed on, when
delegation is implemented this way?

The delegation is done before the call, in order to find the (delegated-to
method) object on which to make the call. The alternative is to write a
wrapper method for each method delegated to and explicitly pass the args on
with *args and ** args. But doing the delegation before the call in the
method lookup make this not necessary and is much easier.

Terry J. Reedy
 
R

Rene Pijlman

Terry Reedy:
The delegation is done before the call, in order to find the (delegated-to
method) object on which to make the call.

Ah, this is the missing link between my brain cells :)

Thank you all for your kind explanation.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top