How functions become bound or unbound methods
Suppose that you have a class:
class A(object): def fred(self, a): print "fred", self, a
Then we have:
>>> a = A() >>> A.fred <unbound method A.fred> >>> b = a.fred >>> b <bound method A.fred of <__main__.A object at 0x1b9c210>>
An unbound method is essentially a function with some trimmings.
A 'bound method' is called that because the first argument (ie
self
) is already set to a
; you can call b(10)
and it works
just the same way as if you had done a.fred(10)
(this is actually
necessary given how CPython operates). So far so good, but how
does Python make this all work?
One way that people sometimes explain how Python makes this work
is to say that A.fred
has been turned into a Python descriptor. This is sort of
true but it is not quite the full story. What is really going on
is that functions are leading a double life: functions are also
descriptors. All functions are descriptors all of the time,
whether or not they're in a class. At this point you might rationally
ask how a bare function (outside of a class) manages to still work;
after all, when you look at it or try to call it, shouldn't the
descriptor stuff kick in? The answer is descriptors only work
inside classess. Outside of classes, descriptors just sort of sit
there and you can access them without triggering their special
behavior; in the case of functions, this means that you can call
them (and look at their attributes if you feel so inclined).
So the upshot is that if you look at a function outside of a class, it is a function and you can do all of the regular functiony things with it. If you look at it inside a class it instantly wraps itself up inside a bound or unbound method (which you can then pry the original function back out of if you're so inclined). This also neatly explains why other callables don't get wrapped up as bound or unbound methods; they aren't (normally) also descriptors that do this.
This is rather niftier than I expected it to be when I started digging. I'm impressed with Python's cleverness here; I would never have expected function objects to be living a double life. And I have to agree that this is an elegantly simple way to make everything work out just right.
(This entry was inspired by a question Pete Zaitcev asked, which started me wondering about how things actually worked.)
PS: All of this is actually described in the descriptor documentation in the Functions and Methods section. I just either never read that or never fully understood it (or forgot it since then).
Sidebar: Why CPython has to expose bound methods
Given how CPython makes calls, returning bound methods all the time is actually utterly essential. CPython transforms Python code to bytecode and in its bytecode there is no 'call <name>' operation; instead you look up <name> with a general lookup operation and then call the result. Since the attribute lookup doesn't know what the looked up value is going to be used for, it has to always return a bound method.
Of course, bound methods are also the right thing to do with method
functions in general if you believe that functions are first class
entities. It'd be very strange for 'b = a.fred; a.fred(10); b(10)
' to
hav the two function calls behave differently.
(The argument over returning unbound methods instead of the bare function is a bit more abstract but I think it's probably the right thing to do.)
|
|