|Date:||15 January 2012|
Let me explain by starting with a simple example. You are already familiar with operator precedence, and how multiplication binds more tightly than addition when both operators appear in the same expression. In the following expression, a will first be multiplied with b, then the result of that operation will be added to c.
There is, in other words, a hidden intermediate result inside of this equation: the result of the multiplication. So (1) is, in fact, a shorthand for writing this sequence of two separate binary operations:
Note that our ability to transform (1) into the pair of lines (2) does not involve any special properties of the operators themselves. This does not illustrate some special feature of multiplication or addition, like the Distributive Property! Instead, we are working down at the lower and more fundamental level of asking what a complex math expression even means. So while we must use argument and proof to learn that addition is commutative, the operators and their precedence are simply a matter of definition — of what we decide it means when we string symbols together to form an expression in the first place.
(3) n = a.b(c)
Until a programmer really grasps what it means for a language to have “first-class functions” — functions that can themselves be manipulated as values — it might be difficult to see that a.b makes quite good sense simply standing by itself. It means “take the a object, look and see whether it has an attribute named b, and resolve the value of that attribute.” And so a.b works perfectly in front of (c) so long as the result of the attribute lookup happens to return a callable.
So expression (3) can be decomposed like expression (1), and in Python the following two steps are exactly equivalent to statement (3) — except, of course, for defining an extra local variable fn:
(4) fn = a.b n = fn(c)
One last note for newer Python programmers reading this: you might be suspecting that Python itself has some kind of magic involved here, because how else could it remember later whether you had pulled method fn off of the specific object a instead of off some other instance of that class? The answer is that every lookup of an instance method returns a new object, called a bound method, that remembers the object on which the lookup took place.
>>> class C: ... def __init__(self, n): ... self.n = n ... def __repr__(self): ... return 'C%d' % self.n ... def fn(self, m): ... return self.n + m ... >>> a = C(100) >>> b = C(220) >>> a.fn <bound method C.fn of C100> >>> b.fn <bound method C.fn of C220> >>> b.fn(5) 225