How functions become bound or unbound methods

March 11, 2014

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.)

Written on 11 March 2014.
« The problem of conditional GET and caches for dynamic websites
The argument about unbound methods versus functions »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Tue Mar 11 23:50:57 2014
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.